Projections using Hypertuned model through XGboost

All data is from FanGraphs. I have no affiliation with FanGraphs, but please consider contributing to their website if you found this project informative.

1 Project Scope

1.1 Objective

This project is designed to showcase how Using a Percentile Based Worth System values Fantasy Baseball Players through a Inning Pitched (IP) weighted projection

The Categories used for prediction valuation are year-end rankings for the following metrics: - HRs - Runs - RBIs - Batting Average - Stolen Bases


2 Processing the Data

2.1 Getting Data Into R

2.1.1 Load Libraries

First we need to load the packages that R needs to run the analysis

library(sqldf) #SQL in R
library(skimr) #Summaries and useful for removing low % data
library(ggplot2) #Plotting Functions
library(plyr) #slightly deprecated data cleaning
library(dplyr) #slightly updated data cleaning
library(tidyverse) #tidyverse data cleaning universe
library(caret) #wrapper for creating, tuning and validating models
library(xgboost) #package for creating regression tree model
library(vtreat) # useful package for treating data before modeling 
library(Matrix)
library(Boruta)
library(mgcv)
library(moments) #for measuring skewness
library(data.table) #alternative to dplyr we use to create lags
library(pdp) #partial dependence graphs
library(vip) #variable importance 
library(grid) #put multiple plots on one grid
library(gridExtra) #additional grid functionality
library(janitor) #one function used to clean transposed data set
library(ggpubr) #for qq plot
library(tableHTML)
library(kableExtra)

The # comments generally explain what additional functionality each library adds to R

2.1.2 Load in Data

All data is downloaded from Fan Graphs. From this location. The data is also available on my Github here. There are player level and team data sets


#data read-in
Batter_data <- read_csv("FanGraphs Leaderboard_Hitting50PA.csv")
#Team datasets
FDG_Team = read_csv("FanGraphs Leaderboard_Team.csv")
#Create a prefix for all team stats that starts with T_
FDG_Team2 <- FDG_Team %>% 
  rename_with( ~ paste0("T_", .x))

2.1.3 Checking Team Data

str give information about an object, while skim provides a customizable summary


#Output not shown for space
#str(FDG_Team2)

skim(FDG_Team2) %>%  
  tibble::as_tibble()
NA
NA

2.2 Understanding the Dataset

2.2.1 Exploring the dataset

skim let’s us see how the data was imported into R. Documentation can be found here


#Full Dataset dimensions

skimr::skim(Batter_data) %>% 
  tibble::as_tibble() %>% 
  select(skim_type,skim_variable,complete_rate) %>% 
  filter(complete_rate >0.30) #288 Variables

#skim_type - character or numeric
#skim_variable - name of variable
#complete_rate - % of data that is not missing
#filter - only keep variables that have 30% of data populated

Additionally let’s look at how variables vary by year to see if there are any discrepancies there


#It looks like one year, there were fewer games played, and there is a clear drop off in home runs
Batter_data_dist =
Batter_data %>% 
 group_by(Season) %>% 
  summarize (Games_played = max(G),
             Avg_HR= mean(HR)
             )
Batter_data_dist

ggplot(Batter_data_dist, aes(Season, Avg_HR)) +
  geom_col()+
  ggtitle("Average Home Runs by Year")+
  theme(plot.title = element_text(hjust = 0.5,size = 22,color ="steel blue"))

NA
NA
NA

2.3 Cleaning and Creating Initial Dataset for Model

What are some issues with the data?

  1. Many of Variables, such as K%, are being read in as characters

    • Only Team and Player Name should be characters
  2. There is spotty data coverage in some of the variables (~Variables have less than 30% Coverage)

  3. 2020 Data only includes 60 games worth of data

    • This was a season shortened due to Covid-19
  4. Team Data needs to be appended to Batter Data by Team Name


2.3.1 Cleanly Changing all Variables that are characters to numeric.

There are several ways to do this, we will identify the variables we want to change that are mis-identified. parse_number can be used to pull numbers from these variables. Additional ways to tackle this can be found here


#Select Column names that are characters but not Team or Name, These should be percentages
Batter_data_chars_to_convert <- Batter_data %>% 
  select_if(is.character)%>% select(-Team,-Name) %>% 
  mutate_all (function(x) as.numeric(readr::parse_number(x))/100)
#Note : There are additional ways to do this, this is just one solution


#We can exclude the variables we converted and reintroduce them
Batter_data_num <- Batter_data %>% select(-colnames(Batter_data_chars_to_convert))

Batter_data2 = cbind(Batter_data_num,Batter_data_chars_to_convert) %>% 
  select (colnames(Batter_data)) %>%  #preserve original order 
  dplyr::rename(flyball_perc = `FB%...46`,fastball_perc = `FB%...73`) #rename two ambiguous columns
  
skim(Batter_data2) %>% 
  as_tibble() %>% 
  group_by(skim_type) %>% 
  count()


#Logical variables are R's best guess, in our case they are all NA's and will be removed

The same can be done for the Team Data that is loaded


#Select Column names that are characters but not Team or Name, These should be percentages
FDG_Team2_chars_to_convert <- FDG_Team2 %>% 
  select_if(is.character)%>% select(-T_Team) %>% 
  mutate_all (function(x) as.numeric(readr::parse_number(x))/100)
#Keep in mind, parse number may make actual characters into numerical variables so carefully check your data before using

#We can exclude the variables we converted and reintroduce them
FDG_Team2_num <- FDG_Team2 %>% select(-colnames(FDG_Team2_chars_to_convert))

FDG_Team3 = cbind(FDG_Team2_num,FDG_Team2_chars_to_convert) %>% 
  select (colnames(FDG_Team2)) %>%  #preserve original order
dplyr::rename(T_flyball_perc = `T_FB%...45`,T_fastball_perc = `T_FB%...72`) 

skim(FDG_Team3) %>% 
  as_tibble() %>% 
  group_by(skim_type) %>% 
  count()
NA
NA
NA

2.3.2 Filtering Data with Low Coverage

I choose 30% coverage of data necessary but this can be adjusted up or down. This will also get rid of columns that are all NA.


# Keep variables with enough values (Need 30% data coverage rate here)
Player_cols_to_keep =
skim(Batter_data2) %>% 
  dplyr::select(skim_type, skim_variable, complete_rate) %>% 
  filter (complete_rate > 0.30)

#Transpose Rows to get column names as skim melts the data
Player_cols_to_keep_transpose = t(Player_cols_to_keep) 

#extract the colnames we would like to keep
Player_cols_to_keep = colnames(janitor::row_to_names(Player_cols_to_keep_transpose,row_number = 2))

#Only keep the columns designated to have over 30% of their data populated or greater
Batter_data3 = Batter_data2 %>% 
  select(one_of(Player_cols_to_keep)) 

Repeat the process for Team Variables

Team_cols_to_keep =
skim(FDG_Team3) %>% 
  dplyr::select(skim_type, skim_variable, complete_rate) %>% 
  filter (complete_rate > 0.30)


#Transpose Rows to get column names as skim melts the data
Team_cols_to_keep_transpose = t(Team_cols_to_keep) 

#extract the colnames we would like to keep
Team_cols_to_keep = colnames(janitor::row_to_names(Team_cols_to_keep_transpose,row_number = 2))

#Only keep the columns designated to have over 30% of their data populated or greater
FDG_Team4 = FDG_Team3 %>% 
  select(one_of(Team_cols_to_keep)) 

2.3.3 Creating Variables Normalized by Year

Some Variables will need to be normalized by Plate Appearances (PA) if they aren’t a percentage already. Remaining Variables are percentages or indicies so will not need to be transformed


Batter_data4 = Batter_data3 %>% 
  mutate( #create new variables based on existing variables
    H_PA = H/PA,
    x1B_PA = `1B`/PA, #note: R can't have variables start with a number
    x2b_PA = `2B`/PA,
    x3b_PA = `3B`/PA,
    HR_PA = HR/PA,
    R_PA = R/PA,
    RBI_PA = RBI/PA,
    BB_PA = BB/PA,
    IBB_PA = IBB/PA,
    SO_PA=SO/PA,
    HBP_PA=HBP/PA,
    SF_PA=SF/PA,
    SH_PA=SH/PA,
    GDP_PA= GDP/PA,#ground into double play
    SB_PA=SB/PA,
    CS_PA=CS/PA,
    GB_PA = GB/PA,   #Groundballs
    FB_PA =  FB/PA,  #FlyBalls
    LD_PA = LD/PA,   #LineDrives
    IFFB_PA = IFFB/PA,  #Infield Fly balls
    Pitches_PA= Pitches/PA,
    Balls_PA= Balls/PA,
    Strikes_PA= Strikes/PA,
    IFH_PA= IFH/PA,
    BU_PA= BU/PA,
    BUH_PA= BUH/PA,
    PH_PA= PH/PA,
    Barrels_PA= Barrels/PA,
    HardHits_PA= HardHit/PA
  ) %>% select(-(H:CS),-(GB:BUH),-PH,-Barrels,-HardHit,-Events) #Drop the old variables

#skim(Batter_data4) %>% as_tibble()

Repeat the process for Team Variables


FDG_Team5 = FDG_Team4 %>% 
  mutate( #create new variables based on existing variables
    T_H_T_PA = T_H/T_PA,
    T_x1B_T_PA = T_1B/T_PA, #note: R can't have variables start with a number
    T_x2b_T_PA = T_2B/T_PA,
    T_x3b_T_PA = T_3B/T_PA,
    T_HR_T_PA = T_HR/T_PA,
    T_R_T_PA = T_R/T_PA,
    T_RBI_T_PA = T_RBI/T_PA,
    T_BB_T_PA = T_BB/T_PA,
    T_IBB_T_PA = T_IBB/T_PA,
    T_SO_T_PA=T_SO/T_PA,
    T_HBP_T_PA=T_HBP/T_PA,
    T_SF_T_PA=T_SF/T_PA,
    T_SH_T_PA=T_SH/T_PA,
    T_GDP_T_PA= T_GDP/T_PA,#ground into double play
    T_SB_T_PA=T_SB/T_PA,
    T_CS_T_PA=T_CS/T_PA,
    T_GB_T_PA = T_GB/T_PA,   #Groundballs
    T_FB_T_PA =  T_FB/T_PA,  #FlyBalls
    T_LD_T_PA = T_LD/T_PA,   #LineDrives
    T_IFFB_T_PA = T_IFFB/T_PA,  #Infield Fly balls
    T_Pitches_T_PA= T_Pitches/T_PA,
    T_Balls_T_PA= T_Balls/T_PA,
    T_Strikes_T_PA= T_Strikes/T_PA,
    T_IFH_T_PA= T_IFH/T_PA,
    T_BU_T_PA= T_BU/T_PA,
    T_BUH_T_PA= T_BUH/T_PA,
    T_PH_T_PA= T_PH/T_PA,
    T_Barrels_T_PA= T_Barrels/T_PA,
    T_HardHits_T_PA= T_HardHit/T_PA
  ) %>% select(-(T_H:T_CS),-(T_GB:T_BUH),-T_PH,-T_Barrels,-T_HardHit,-T_Events) #Drop the old variables


#skim(FDG_Team5) %>% as_tibble()

2.3.4 Creating Lagged Variables

There are several ways to lag a dataset BY GROUP.
* Dplyr way is here..
* While data.table (the method used below) is here.

#Note we will only be lagging the player level data, as the previous year's team performance shouldn't impact current performance


#Order the dataset by lag columns
Batter_data5 =  arrange(Batter_data4, playerid,Season) #playerid is the Fangraph id assigned to each player

# Convert dataframe to data.table format
DT_batter = data.table(Batter_data5)

#designate columns to lag - which is all of them
cols1 = colnames(Batter_data5)
anscols = paste("lag", cols1, sep="_")
DT_batter[, (anscols) := data.table::shift(.SD, 1, NA, "lag"),by ='playerid', .SDcols=cols1] #Create 1 period lags by year

Batter_data6 = as.data.frame(DT_batter) %>% select(-lag_playerid, -lag_Team, -lag_Season, -lag_Age,-lag_Name)

ncol(Batter_data5) #287 - no lags
[1] 259
ncol(Batter_data6) #574 - lagged data ~ (287 * 2)-5
[1] 513

2.3.5 Merging Team and Player Data

We can use either the merge function or the SQL functionality provided by the sqldf package to join the lagged player level data to the Team level data


df_batting_init = sqldf(
  "
  select a.*, b.*
  from Batter_data6 a
  left join FDG_Team5 b
  on a.Team = b.T_Team and a.Season = b.T_Season
  
  "
)  %>% select(-T_Team,-T_Season,T_Age,T_G,T_AB)# Unncessary Team Variables


nrow(df_batting_init) - nrow(Batter_data6) #check if any rows are duplicated
[1] 0

3 Creating Rankings for Players Based On Percentiles

We can use Percentile based ranking to get rankings for players from the 2021 season.

3.1 Worth of each stat

3.1.1 Calculating past performance

Each player goes from a 0% to 100% on each percentile stat that is used for creating a scoring opportunity. All data is already normalized by plate appearances, but must now be ranked for each year.


#Categories I include are:
#Runs (R), Home Runs (HR), Runs Batted In (RBI), Stolen Bases (SB), Batting Average (AVG)
df_batting_init2 =  df_batting_init %>%
#  arrange(player_id,year) %>% 
  group_by(Season) %>% 
  mutate(
    Runs_share = order(order(rank(R_PA,ties.method = 'average'),decreasing = FALSE))/n(),
     HR_share = order(order(rank(HR_PA,ties.method = 'average'),decreasing = FALSE))/n(),
     RBI_share = order(order(rank(RBI_PA,ties.method = 'average'),decreasing = FALSE))/n(),
     SB_share = order(order(rank(SB_PA,ties.method = 'average'),decreasing = FALSE))/n(),
     AVG_share = order(order(rank(AVG,ties.method = 'average'),decreasing = FALSE))/n(),
    OPS_share = 0,
    Worth = Runs_share+HR_share+RBI_share+SB_share+AVG_share+OPS_share
    ) %>% 
  ungroup() 

Chart of the Distribution of initial percentiles
As the chart below shows, the data is roughly normal.


skewness((df_batting_init2$Worth))
[1] -0.25
ggplot2::qplot(df_batting_init2$Worth, main="Total Dataset") + geom_histogram(colour="black", fill="grey") + theme_bw()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

min(df_batting_init2$Worth)
[1] 0.029
max(df_batting_init2$Worth)
[1] 4.8
ggpubr::ggqqplot(df_batting_init2$Worth)


shapiro.test(df_batting_init2$Worth)

    Shapiro-Wilk normality test

data:  df_batting_init2$Worth
W = 1, p-value <0.0000000000000002

3.2 2021 Player Rankings

3.2.1 2021 Player Rankings - Top Worth Players with OPS

Total Rankings for the players (Using 5x5 Scoring) can be found here


options(digits=2)

df_batting_init2021 =
df_batting_init2 %>% 
  group_by(Name) %>% 
  filter(Season == 2021) %>% 
  arrange(desc(Worth)) %>% 
  select(Name,Runs_share,HR_share,RBI_share, SB_share,OPS_share,AVG_share,Worth)


df_batting_init2021 %>%
  filter (Worth>3.9) %>% 
  kbl() %>% 
 kable_material(c("striped", "hover","condensed","responsive"),full_width = F,fixed_thead = T)
Name Runs_share HR_share RBI_share SB_share OPS_share AVG_share Worth
Fernando Tatis Jr. 0.99 1.00 0.98 0.96 0 0.89 4.8
Ronald Acuna Jr. 1.00 0.98 0.82 0.96 0 0.90 4.7
Byron Buxton 1.00 0.99 0.70 0.92 0 0.97 4.6
Tyler O'Neill 0.96 0.97 0.86 0.88 0 0.91 4.6
Jose Ramirez 0.98 0.93 0.94 0.94 0 0.78 4.6
Teoscar Hernandez 0.92 0.90 1.00 0.80 0 0.94 4.6
Kyle Tucker 0.87 0.89 0.94 0.86 0 0.94 4.5
Bryce Harper 0.96 0.95 0.80 0.82 0 0.97 4.5
Bo Bichette 0.99 0.76 0.85 0.92 0 0.95 4.5
Shohei Ohtani 0.95 0.99 0.90 0.94 0 0.69 4.5
Javier Baez 0.87 0.93 0.92 0.91 0 0.77 4.4
Frank Schwindel 0.97 0.90 0.95 0.55 0 0.99 4.4
Vladimir Guerrero Jr. 0.99 0.98 0.92 0.49 0 0.98 4.4
Trea Turner 0.96 0.78 0.63 0.97 0 1.00 4.3
Brandon Crawford 0.85 0.78 0.94 0.80 0 0.95 4.3
Nick Castellanos 0.95 0.94 0.97 0.47 0 0.97 4.3
Marcus Semien 0.94 0.96 0.80 0.81 0 0.77 4.3
Juan Soto 0.96 0.80 0.83 0.70 0 0.99 4.3
Luis Robert 0.84 0.79 0.83 0.81 0 1.00 4.3
Brandon Belt 0.97 0.99 0.89 0.56 0 0.85 4.3
Paul Goldschmidt 0.90 0.83 0.84 0.76 0 0.94 4.3
Rafael Devers 0.91 0.93 0.97 0.55 0 0.88 4.2
Manny Machado 0.85 0.79 0.95 0.78 0 0.88 4.2
Jorge Polanco 0.90 0.88 0.88 0.76 0 0.80 4.2
A.J. Pollock 0.64 0.87 0.94 0.82 0 0.95 4.2
George Springer 0.98 0.97 0.84 0.66 0 0.76 4.2
Mike Trout 0.93 0.91 0.67 0.69 0 1.00 4.2
Aaron Judge 0.83 0.96 0.88 0.60 0 0.92 4.2
Ozzie Albies 0.90 0.78 0.88 0.89 0 0.71 4.2
Matt Olson 0.89 0.94 0.94 0.50 0 0.83 4.1
Adam Engel 0.89 0.88 0.72 0.97 0 0.64 4.1
Thairo Estrada 0.85 0.89 0.96 0.55 0 0.85 4.1
Brandon Lowe 0.93 0.97 0.93 0.66 0 0.59 4.1
Avisail Garcia 0.73 0.92 0.96 0.72 0 0.74 4.1
Freddie Freeman 0.98 0.80 0.64 0.66 0 0.95 4.0
Yordan Alvarez 0.91 0.91 0.98 0.34 0 0.87 4.0
Tim Anderson 0.97 0.57 0.54 0.90 0 0.98 4.0
Jesse Winker 0.93 0.87 0.84 0.36 0 0.96 4.0
Kyle Schwarber 0.95 0.98 0.87 0.36 0 0.78 3.9
Chas McCormick 0.88 0.79 0.90 0.68 0 0.69 3.9
Jake Meyers 0.77 0.68 0.98 0.77 0 0.72 3.9
NA

4 Creating Model File

4.1 Additional Data Prep

4.1.1 Remove Variables which are based off current hitting numbers

Not all variables can be used for predictive modeling.

df_batting_init3 = df_batting_init2

Lag Share Variables to use for predictive modeling. The variables that we created for the Worth metric must also be removed. This will create the final dataset.

4.1.2 Creating Training/Test Split

We split the data into Training Data (which is used to create the model) and test data (which is used to validate the model)


set.seed(15674)  # For reproducibility
# Create index for testing and training data
inTrain <- createDataPartition(y = df_batting_final$Worth, p = 0.80, list = FALSE)
# subset pitching data for training
tr_2021 <- df_batting_final[inTrain,]
# subset the rest to test and validate trained model
te_2021 <- df_batting_final[-inTrain,]

nrow(tr_2021)/nrow(df_batting_final) #check if split is 0.8
[1] 0.8

4.1.3 Treat Missing Data by Imputing Mean Value

Vtreat Package in R is excellent for treating data before using for modeling. Additional documentation can be found here.

treat_plan_2021 <- vtreat::designTreatmentsZ(
  dframe = tr_2021, # training data
  varlist = colnames(tr_2021) %>% .[. != "hitting_score1"], # input variables = all training data columns, except random
  codeRestriction = c("clean", "isBAD", "lev"), # derived variables types (drop cat_P)
  verbose = FALSE) # suppress messages

#clean stands for cleaned numerical variable, isBAD indicates that a value replacement has occurred (which indicates a missing value in this case), and lev is a binary indicator whether a particular value of that categorical variable was present.  

#### Checking Scoreframe

score_frame <- treat_plan_2021$scoreFrame %>% 
  select(varName, origName, code)

head(score_frame)


tr_treated_2021 <- vtreat::prepare(treat_plan_2021, tr_2021)
te_treated_2021 <- vtreat::prepare(treat_plan_2021, te_2021)


treat_plan_2021 <- vtreat::designTreatmentsZ(
  dframe = DT_pitcher2, # training data
  varlist = colnames(DT_batter2) %>% .[. != "hitting_score1"], # input variables = all training data columns, except random
  codeRestriction = c("clean", "isBAD", "lev"), # derived variables types (drop cat_P)
  verbose = FALSE) # suppress messages


total_treated_2021_hitting <- vtreat::prepare(treat_plan_2021, DT_batter2)



#tr_treated = tr
#te_treated = te

dim(tr_treated_2021) #note there are dummies for each player and team
[1] 3424 1454

4.1.4 Check Distribution of Training Population

The population used for Training should be indicative of Total Population


ggplot2::qplot(tr_treated_2021$Worth, main="Training Set") + geom_histogram(colour="black", fill="grey") + theme_bw()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

skewness(tr_treated_2021$Worth) #The skewness is the same as the overall
[1] -0.24

5 Running XGboost Model

To keep things simple with modeling, we’ll turn the training data into simple input variables for caret::train, dropping the response variable and converting the data frame to a matrix. Documentation for this approach to XGboost can be found here.

5.1 Tuning the Model

5.1.1 Initial Non-Tuned Model

Break the data set into x and y inputs with x being a matrix

input_x <- as.matrix(((tr_treated_2021))%>%
   select(-Worth) %>%                      
   select(!ends_with ("_isBAD")))

input_y <- tr_treated_2021$Worth

XGBoost with Default Hyperparameters
The Variable Importance (caret::varImp(xgb_base_2021, scale = F )) from the caret package shows the contribution of each variable to the initial model. As you can see SLG_plus_ (SLG+) takes up much of the importance as it is derived from SLG (one of the key contributors to Worth). These types of variables will be removed during variable selection in the next step.
XGBoost documentation can be found for more general models here.


#Defaults for xgboost model
grid_default <- expand.grid(
  nrounds = 100,
  max_depth = 6,
  eta = 0.3,
  gamma = 0,
  colsample_bytree = 1,
  min_child_weight = 1,
  subsample = 1
)

#This is a blank train_control set, this will be updated after
train_control <- caret::trainControl(
  method = "none",
  verboseIter = FALSE, # no training log
  allowParallel = TRUE # FALSE for reproducible results 
)

xgb_base_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = train_control,
  tuneGrid = grid_default,
  method = "xgbTree",
  verbose = TRUE
)

caret::varImp(xgb_base_2021, scale = F  )
xgbTree variable importance

  only 20 most important variables shown (out of 812)

5.2 Further Variable Selection

5.2.1 Remove redundant and highly correlated variables

Selection Removal Step 1: Check for high correlations
Normally, this step is done early, but those steps were reserved for preparing the data


dep_cor1 <- t(as.data.frame(cor(tr_treated_2021[ , colnames(tr_treated_2021) != "Worth"],
                tr_treated_2021$Worth)))
dep_cor1 <-
as.data.frame(t(as.data.frame(dep_cor1)%>% 
  select(!starts_with("lag")) %>% #remove lag variables
  select(!contains("_isBAD")))) 

dep_cor1 <- tibble::rownames_to_column(dep_cor1,"VARIABLES")%>% #remove indicators for missing data
  filter(V1 > 0.70|V1 < -0.5)

dep_cor1

dep_cor2 <- colnames(row_to_names(t(dep_cor1),row_number = 1))

Let’s Remove variables with high correlation to worth metric, and metrics that are calculated after a player’s performance (such as WAR)


input_x <- as.matrix(((tr_treated_2021))%>%
   select(-Worth) %>% #Remove dependant variables
     select (-all_of(dep_cor2),-RAR,-WAR,-WPA,-Spd,-Rep,-BABIP,-Def,-BABIP_plus_,-PA,-AB,-G,-wFB,   -wSL,   -wCT,   -wCH,   -wSF, -X_plus_WPA, -wSB, -BsR,-Pos,-UBR , -wCB #Remove redundant variables or non/weighted variables
) %>%      
select(!ends_with ("_isBAD"))) #indicator variable for missing data

input_y <- tr_treated_2021$Worth

Run the model on the new dataset to make sure the variable importances look fine


#Note Training parameters were set in initial model set up
xgb_base_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = train_control,
  tuneGrid = grid_default,
  method = "xgbTree",
  verbose = TRUE
)

caret::varImp(xgb_base_2021, scale = F  )
xgbTree variable importance

  only 20 most important variables shown (out of 776)

5.3 Model with new data

5.3.1 Tuning All Hyperparameters

A tune grid allows us to test a large amount of hyper-parameters and find the model with the lowest RMSE for predictions.
However, The more values you want to test and the greater the amount of Cross-Fold Validations (method = "cv"), the greater the computational time it will take. More information on the specific parameters can be found here.


# maximum number of trees
nrounds <- 1000

# note to start nrounds from 200, as smaller learning rates result in errors so
# big with lower starting points that they'll mess the scales
tune_grid <- expand.grid(
  nrounds = seq(from = 100, to = nrounds, by = 50),
  eta = c(0.01, 0.025, 0.05, 0.1),
  max_depth = c(2, 4, 6, 8),
  gamma = 0,
  colsample_bytree = 1,
  min_child_weight = 1,
  subsample = 1
)

tune_control <- caret::trainControl(
  method = "cv", # cross-validation
  number = 5, # with n folds 
  ## Note this was # out in the original code
  #index = createFolds(tr_treated$Id_clean), # fix the folds
  verboseIter = FALSE, # no training log
  allowParallel = TRUE # FALSE for reproducible results 
)

Running the initial tuning model

#Note I will be timing these runs to give an estimate on how long this model takes to run
start_time <- Sys.time()

xgb_tune_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid,
  method = "xgbTree",
  verbose = FALSE
  ,verbosity = 0
)

end_time <- Sys.time()

end_time - start_time
Time difference of 13 mins

Tuning Plot and Variable Importance

varImp(xgb_tune_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 776)
# helper function for the plots
tuneplot <- function(x, probs = .90) {
  ggplot(x) +
    coord_cartesian(ylim = c(quantile(x$results$RMSE, probs = probs), min(x$results$RMSE))) +
    theme_bw()
}

tuneplot(xgb_tune_2021)

5.3.2 Fine Tuning Model

5.3.2.1 Second Tuning: Maximum Depth and Minimum Child Weight

After fixing the learning rate to 0.1 and we’ll also set maximum depth to 3 +-1 (or +2 if max_depth == 2) to experiment a bit around the suggested best tune in previous step. Then, well fix maximum depth and minimum child weigh

tune_grid2 <- expand.grid(
  nrounds = seq(from = 50, to = nrounds, by = 50),
  eta = xgb_tune_2021$bestTune$eta,
  max_depth = ifelse(xgb_tune_2021$bestTune$max_depth == 2,
    c(xgb_tune_2021$bestTune$max_depth:4),
    xgb_tune_2021$bestTune$max_depth - 1:xgb_tune_2021$bestTune$max_depth + 1),
  gamma = 0,
  colsample_bytree = 1,
  min_child_weight = c(1, 2, 3),
  subsample = 1
)

xgb_tune2_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid2,
  method = "xgbTree",
  verbose = TRUE
)

tuneplot(xgb_tune2_2021)


xgb_tune2_2021$bestTune

varImp(xgb_tune2_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 776)

5.3.2.2 Third Tuning: Column and Row Sampling


tune_grid3 <- expand.grid(
  nrounds = seq(from = 50, to = nrounds, by = 50),
  eta = xgb_tune_2021$bestTune$eta,
  max_depth = xgb_tune2_2021$bestTune$max_depth,
  gamma = 0,
  colsample_bytree = c(0.4, 0.6, 0.8, 1.0),
  min_child_weight = xgb_tune2_2021$bestTune$min_child_weight,
  subsample = c(0.5, 0.75, 1.0)
)

xgb_tune3_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid3,
  method = "xgbTree",
  verbose = TRUE
)

tuneplot(xgb_tune3_2021, probs = .95)


xgb_tune3_2021$bestTune

varImp(xgb_tune3_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 776)

5.3.2.3 Fourth Tuning: Gamma

Next, we again pick the best values from previous step, and now will see whether changing the gamma has any effect on the model fit:

tune_grid4 <- expand.grid(
  nrounds = seq(from = 50, to = nrounds, by = 50),
  eta = xgb_tune_2021$bestTune$eta,
  max_depth = xgb_tune2_2021$bestTune$max_depth,
  gamma = c(0, 0.05,0.1, 0.2,0.4, 0.5, 0.7, 0.9, 1.0),
  colsample_bytree = xgb_tune3_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune2_2021$bestTune$min_child_weight,
  subsample = xgb_tune3_2021$bestTune$subsample
)

xgb_tune4_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid4,
  method = "xgbTree",
  verbose = TRUE
)

tuneplot(xgb_tune4_2021)
Warning: The shape palette can deal with a maximum of 6 discrete values because more than 6
becomes difficult to discriminate; you have 9. Consider specifying shapes manually if you
must have them.
Warning: Removed 60 rows containing missing values (geom_point).

xgb_tune4_2021$bestTune

varImp(xgb_tune4_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 776)

5.3.2.4 Fifth Tuning: Reducing the Learning Rate

Now, we have tuned the hyperparameters and can start reducing the learning rate to get to the final model:

start_time <- Sys.time()

tune_grid5 <- expand.grid(
  nrounds = seq(from = 100, to = 10000, by = 75),
   eta = c(0.01, 0.015, 0.025,0.035, 0.05,0.75, 0.1),
  max_depth = xgb_tune2_2021$bestTune$max_depth,
  gamma = xgb_tune4_2021$bestTune$gamma,
  colsample_bytree = xgb_tune3_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune2_2021$bestTune$min_child_weight,
  subsample = xgb_tune3_2021$bestTune$subsample
)



xgb_tune5_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = tune_control,
  tuneGrid = tune_grid5,
  method = "xgbTree",
  verbose = TRUE
)

#tuneplot(xgb_tune5_2021)

end_time <- Sys.time()

end_time - start_time
Time difference of 17 mins
xgb_tune5_2021$bestTune

varImp(xgb_tune5_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 776)

5.3.2.5 Fitting Final Model


(final_grid_2021 <- expand.grid(
  nrounds = xgb_tune5_2021$bestTune$nrounds,
  eta = xgb_tune5_2021$bestTune$eta,
  max_depth = xgb_tune5_2021$bestTune$max_depth,
  gamma = xgb_tune5_2021$bestTune$gamma,
  colsample_bytree = xgb_tune5_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune5_2021$bestTune$min_child_weight,
  subsample = xgb_tune5_2021$bestTune$subsample
))

(xgb_model_2021 <- caret::train(
  x = input_x,
  y = input_y,
  trControl = train_control,
  tuneGrid = final_grid_2021,
  method = "xgbTree",
  verbose = TRUE
))
eXtreme Gradient Boosting 

3424 samples
 776 predictor

No pre-processing
Resampling: None 
varImp(xgb_model_2021, scale = F  ) 
xgbTree variable importance

  only 20 most important variables shown (out of 776)

5.3.3 Model Performance

5.3.3.1 Checking Model on Test Split Data



y_pred_test <- predict(xgb_model_2021, data.matrix(te_treated_2021))

test_stats= cbind((te_treated_2021$Worth),y_pred_test)

test_statsR2 = cor(test_stats[,1],test_stats[,2])^2

print(test_statsR2)
[1] 0.86
y_pred_train <- predict(xgb_model_2021, data.matrix(tr_treated_2021))

train_stats = cbind((tr_treated_2021$Worth),y_pred_train)

train_statsR2 = cor(train_stats[,1],train_stats[,2])^2

print(train_statsR2)
[1] 0.95
#test dataset
x <- select(te_treated_2021, -Worth)
y <- (te_treated_2021$Worth)

(xgb_model_rmse <- ModelMetrics::rmse(y, predict(xgb_model_2021, newdata = x)))
[1] 0.37
holdout_x <- select(tr_treated_2021, -Worth)
holdout_y <- tr_treated_2021$Worth

(xgb_model_rmse <- ModelMetrics::rmse(holdout_y, predict(xgb_model_2021, newdata = holdout_x)))
[1] 0.23

5.3.3.2 Graphical Representation of Model


ggplot2::ggplot() +
  aes(x = test_stats[,1], y = test_stats[,2]) +
  geom_jitter() +
  xlab("Predicted Values") +
  ylab("Actual Values") +
  ggtitle("Results of Pitching Model on Test Data")+
  theme(plot.title = element_text(hjust = 0.5,size = 22,color ="steel blue"))+
  geom_smooth(method = "lm")
`geom_smooth()` using formula 'y ~ x'

6 Creating 2022 Projections from Model

6.1 Re-fit model for Important Variables

Now that we have an acceptable model, we can use it to create projections for how well we think players should do in 2022 based on their hitting statistics in 2021. First let’s reduce

  1. Only keep variables with high enough importance in model


vip(xgb_model_2021, num_features = 30)  # 10 is the default, 30 gives a visual on the top 30 most important features of the model


unscalevi = vi(xgb_model_2021, method="model") #shows the numbers behind the plot

unscalevi$Importance_perc = with(unscalevi,Importance/sum(Importance)) #adds percentages 

unscalevi # importance by variables

variables_to_keep_2021 = subset(unscalevi, Importance_perc > 0.0010) %>% select(Variable) #Keep Variables that explain at least a small amount [0.1%] of the model. This is a low threshold for inclusion ,but you can adjust this

variables_to_keep_2021b = t(variables_to_keep_2021)

variables_to_keep_2022 = colnames(row_to_names(variables_to_keep_2021b,row_number = 1))

tr_treated_2022 = tr_treated_2021 %>%  select(Worth,one_of(variables_to_keep_2022),starts_with("Team_lev_x_")) #keep modeled important variables along with team indicator variables

te_treated_2022 = te_treated_2021 %>%  select(Worth,one_of(variables_to_keep_2022),starts_with("Team_lev_x_"))

input_x_2022 = as.matrix(select(tr_treated_2022, -Worth))

input_y_2022 = tr_treated_2022$Worth
  1. Re-fit model with reduced variable scope


(final_grid_2021 <- expand.grid(
  nrounds = xgb_tune5_2021$bestTune$nrounds,
  eta = xgb_tune5_2021$bestTune$eta,
  max_depth = xgb_tune5_2021$bestTune$max_depth,
  gamma = xgb_tune5_2021$bestTune$gamma,
  colsample_bytree = xgb_tune5_2021$bestTune$colsample_bytree,
  min_child_weight = xgb_tune5_2021$bestTune$min_child_weight,
  subsample = xgb_tune5_2021$bestTune$subsample
))

(xgb_model_2022 <- caret::train(
  x = input_x_2022,
  y = input_y_2022,
  trControl = train_control,
  tuneGrid = final_grid_2021,
  method = "xgbTree",
  verbose = TRUE
))
eXtreme Gradient Boosting 

3424 samples
  88 predictor

No pre-processing
Resampling: None 
vip(xgb_model_2022, num_features = 30)


unscalevi24 = vi(xgb_model_2022, method="model")

unscalevi24$Importance_perc = with(unscalevi24,Importance/sum(Importance)) 

unscalevi24


#For anything above breaking_IP we need to create projection table by age or age bucket

#write_csv(unscalevi24,"unscalevi24.csv")
# 2022 Projections Full
First let’s prepare a file for predicting based on our model object
```r
variableslag5xb= row_to_names(as.data.frame(t(variables_to_keep_2022)),row_number = 1) %>% select (starts_with(“lag”))
variables_nolag5xb = (owmr::remove_prefix(variableslag5xb,“lag” , sep = “_“))
Data_Predict_2022a5xb = total_treated_2021_hitting %>% select (one_of(colnames(variables_nolag5xb)),Season,playerid)
colnames(Data_Predict_2022a5xb) <- paste0(“lag_”, colnames(Data_Predict_2022a5xb))
Data_Predict_2022b5xb = total_treated_2021_hitting %>% select (one_of(colnames(variables_nolag5xb))) colnames(Data_Predict_2022b5xb) = colnames(variableslag5xb)
variables_to_keep_2022_nolag5xb = total_treated_2021_hitting %>% select(one_of(variables_to_keep_2022),Season,playerid,starts_with(“Team_lev_x_”))%>% select(-one_of(colnames(Data_Predict_2022b5xb)))
Data_predict_20225xb = sqldf( ” select a.,b. from Data_Predict_2022a5xb a, variables_to_keep_2022_nolag5xb b on b.playerid = a.lag_playerid and b.Season = a.lag_Season ” ) %>% select(-lag_playerid,lag_Season) %>% filter(Season == 2021) %>% select(one_of(variables_to_keep_2022),starts_with(“Team_lev_x_”))
```

6.2 Create Predictions for Model

6.2.1 Run Projections on Players who Played in 2021

This is the raw prediction score per IP for each pitcher


hitting_predictions5xb = as.data.frame(predict(xgb_model_2022,Data_predict_20225xb))

names(hitting_predictions5xb) = c("Predict_Score")

Data_predict_2022_w_hitting_Predictions5xb = cbind(Data_predict_20225xb,hitting_predictions5xb) %>% select(playerid,Predict_Score)

head(Data_predict_2022_w_hitting_Predictions5xb)
NA


Latest_2022_hittingdata_FP = read_csv("FanGraph_Fantasy_Baseball_Hitting.csv")
Rows: 625 Columns: 28
-- Column specification ----------------------------------------------------------------------------------
Delimiter: ","
chr  (3): Name, Team, playerid
dbl (25): G, PA, AB, H, 2B, 3B, HR, R, RBI, BB, SO, HBP, SB, CS, AVG, OBP, SLG, OPS, wOBA, wRC+, WAR, ...

i Use `spec()` to retrieve the full column specification for this data.
i Specify the column types or set `show_col_types = FALSE` to quiet this message.
Latest_2022_hittingdata_FP
NA
NA



hitting_Data_NonAdj_Projections5xb = sqldf(
  "
  select a.*,b.Predict_Score
  from Latest_2022_hittingdata_FP a 
  left join 
  Data_predict_2022_w_hitting_Predictions5xb b
  on a.playerid = b.playerid
  "
) %>% filter(ADP<370 | is.na(Predict_Score)==F)


hitting_Data_Adj_Projections5xb =
hitting_Data_NonAdj_Projections5xb %>% 
  mutate(
    Avg_PA = 300,
    AdjPredict_Score_raw = ifelse(is.na(Predict_Score),NA,Predict_Score*(PA/Avg_PA)),
    max_predscore= max(AdjPredict_Score_raw,na.rm = T),
    AdjPredict_Score = ifelse (is.na(AdjPredict_Score_raw),NA,AdjPredict_Score_raw *100/max_predscore),
    WAR_rank = order(order(rank(WAR,ties.method = 'average'),decreasing = TRUE)),
    AdjPredict_Score_Rank = order(order(rank(AdjPredict_Score,ties.method = 'average'),decreasing = TRUE))-sum(is.na(AdjPredict_Score))
  ) %>% select (Name,ADP,WAR, WAR_rank,AdjPredict_Score ,AdjPredict_Score_Rank)


  

ggplot2::qplot(hitting_Data_Adj_Projections5xb$AdjPredict_Score, main="Predictions") + geom_histogram(colour="black", fill="grey") + theme_bw()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.


7 2022 Projections Full

7.1 Table of hitting Projections (Players who Didn’t Play in 2021 - Recieve an NA)

AdjPredict_Score are normalized to 100


tableexport =
hitting_Data_Adj_Projections5xb %>%
  arrange (ADP,WAR) %>% 
  kbl() %>% 
 kable_material(c("striped", "hover","condensed","responsive"),full_width = F,fixed_thead = T)

save_kable(tableexport,file = "hitting5x5.html")

#tableexport

This is a better formatted Table



ft_dt <- hitting_Data_Adj_Projections5xb[1:nrow(hitting_Data_Adj_Projections5xb), 1:ncol(hitting_Data_Adj_Projections5xb)]

ft_dt$ADP <- color_tile("white", "red")(ft_dt$ADP)

ft_dt$WAR <- color_bar("lightblue")(ft_dt$WAR)

ft_dt$Predict_Score <- cell_spec(round(ft_dt$AdjPredict_Score,1), angle = (1:5)*0, 
                     background = "green", color = "white", align = "center")

ft_dt$WAR_Rank <- color_tile("green","orange")(ft_dt$WAR_rank)

ft_dt$Predict_Rank <- color_tile("green","orange")(ft_dt$AdjPredict_Score_Rank)

ft_dt <- ft_dt[c("Name", "ADP", "WAR", "Predict_Score","WAR_Rank","Predict_Rank")] %>% arrange (desc(ADP))


table_export = 
kbl(ft_dt, escape = F) %>% 
 kable_material(c("striped", "hover","condensed","responsive"),full_width = F,fixed_thead = T) %>%   column_spec(6, width = "3cm") %>%
  add_header_above(c(" ", "Scores" = 3, "Ranks" = 2))

save_kable(table_export,file = "hitting5x5_updated.html")
LS0tDQp0aXRsZTogIldlbGNvbWUgdG8gbXkgMjAyMiBQcm9qZWN0aW9ucyBmb3IgSGl0dGVycyA1eDUiDQphdXRob3I6ICJEYXJzaGFuIFBhdGVsIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0aGVtZTogc2FuZHN0b25lDQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KICAgIGZpZ19jYXB0aW9uOiB0cnVlDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCg0KPGh0bWw+DQoNCjxwPg0KDQpQcm9qZWN0aW9ucyB1c2luZyBIeXBlcnR1bmVkIG1vZGVsIHRocm91Z2ggWEdib29zdA0KDQo8L3A+DQoNCjxwPg0KDQpBbGwgZGF0YSBpcyBmcm9tIFtGYW5HcmFwaHMuXShodHRwczovL3d3dy5mYW5ncmFwaHMuY29tLykgSSBoYXZlIG5vIGFmZmlsaWF0aW9uIHdpdGggRmFuR3JhcGhzLCBidXQgcGxlYXNlIGNvbnNpZGVyIGNvbnRyaWJ1dGluZyB0byB0aGVpciBbd2Vic2l0ZV0oaHR0cHM6Ly9wbHVzLmZhbmdyYXBocy5jb20vc2hvcC8pIGlmIHlvdSBmb3VuZCB0aGlzIHByb2plY3QgaW5mb3JtYXRpdmUuDQoNCjwvcD4NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQprbml0cjo6b3B0c19rbml0JHNldChyb290LmRpciA9ICdDOi9Vc2Vycy9BZG1pbi9Eb2N1bWVudHMvTGVhcm5pbmcgUHl0aG9uIEZvbGRlcjEvUHl0aG9uIEVzc2VuY2UgVHJhaW5pbmcvRmFudGFzeS1CYXNlYmFsbCcpDQpvcHRpb25zKGtuaXRyLnRhYmxlLmZvcm1hdCA9ICJodG1sIikgDQpvcHRpb25zKGRpZ2l0cz0yKQ0Kb3B0aW9ucyhzY2lwZW4gPSAxMDApDQpgYGANCg0KDQojIFByb2plY3QgU2NvcGUgey50YWJzZXR9DQoNCiMjIE9iamVjdGl2ZQ0KDQpUaGlzIHByb2plY3QgaXMgZGVzaWduZWQgdG8gc2hvd2Nhc2UgaG93IFVzaW5nIGEgUGVyY2VudGlsZSBCYXNlZCBXb3J0aCBTeXN0ZW0gdmFsdWVzIEZhbnRhc3kgQmFzZWJhbGwgUGxheWVycyB0aHJvdWdoIGEgSW5uaW5nIFBpdGNoZWQgKElQKSB3ZWlnaHRlZCBwcm9qZWN0aW9uDQoNClRoZSBDYXRlZ29yaWVzIHVzZWQgZm9yIHByZWRpY3Rpb24gdmFsdWF0aW9uIGFyZSB5ZWFyLWVuZCByYW5raW5ncyBmb3IgdGhlIGZvbGxvd2luZyBtZXRyaWNzOg0KLSAgIEhScw0KLSAgIFJ1bnMNCi0gICBSQklzDQotICAgQmF0dGluZyBBdmVyYWdlDQotICAgU3RvbGVuIEJhc2VzDQoNCiFbXShJbnRyb0NoYXJ0Nng2LnBuZykNCg0KDQoqKiogIA0KDQojIFByb2Nlc3NpbmcgdGhlIERhdGEgey50YWJzZXR9DQoNCiMjIEdldHRpbmcgRGF0YSBJbnRvIFINCg0KIyMjIExvYWQgTGlicmFyaWVzDQoNCjxwIHN0eWxlPSJjb2xvcjpibGFjazsiPg0KDQoqRmlyc3Qgd2UgbmVlZCB0byBsb2FkIHRoZSBwYWNrYWdlcyB0aGF0IFIgbmVlZHMgdG8gcnVuIHRoZSBhbmFseXNpcyoNCg0KPC9wPg0KDQpgYGB7ciBsb2FkIGxpYnJhcnksbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHNxbGRmKSAjU1FMIGluIFINCmxpYnJhcnkoc2tpbXIpICNTdW1tYXJpZXMgYW5kIHVzZWZ1bCBmb3IgcmVtb3ZpbmcgbG93ICUgZGF0YQ0KbGlicmFyeShnZ3Bsb3QyKSAjUGxvdHRpbmcgRnVuY3Rpb25zDQpsaWJyYXJ5KHBseXIpICNzbGlnaHRseSBkZXByZWNhdGVkIGRhdGEgY2xlYW5pbmcNCmxpYnJhcnkoZHBseXIpICNzbGlnaHRseSB1cGRhdGVkIGRhdGEgY2xlYW5pbmcNCmxpYnJhcnkodGlkeXZlcnNlKSAjdGlkeXZlcnNlIGRhdGEgY2xlYW5pbmcgdW5pdmVyc2UNCmxpYnJhcnkoY2FyZXQpICN3cmFwcGVyIGZvciBjcmVhdGluZywgdHVuaW5nIGFuZCB2YWxpZGF0aW5nIG1vZGVscw0KbGlicmFyeSh4Z2Jvb3N0KSAjcGFja2FnZSBmb3IgY3JlYXRpbmcgcmVncmVzc2lvbiB0cmVlIG1vZGVsDQpsaWJyYXJ5KHZ0cmVhdCkgIyB1c2VmdWwgcGFja2FnZSBmb3IgdHJlYXRpbmcgZGF0YSBiZWZvcmUgbW9kZWxpbmcgDQpsaWJyYXJ5KE1hdHJpeCkNCmxpYnJhcnkoQm9ydXRhKQ0KbGlicmFyeShtZ2N2KQ0KbGlicmFyeShtb21lbnRzKSAjZm9yIG1lYXN1cmluZyBza2V3bmVzcw0KbGlicmFyeShkYXRhLnRhYmxlKSAjYWx0ZXJuYXRpdmUgdG8gZHBseXIgd2UgdXNlIHRvIGNyZWF0ZSBsYWdzDQpsaWJyYXJ5KHBkcCkgI3BhcnRpYWwgZGVwZW5kZW5jZSBncmFwaHMNCmxpYnJhcnkodmlwKSAjdmFyaWFibGUgaW1wb3J0YW5jZSANCmxpYnJhcnkoZ3JpZCkgI3B1dCBtdWx0aXBsZSBwbG90cyBvbiBvbmUgZ3JpZA0KbGlicmFyeShncmlkRXh0cmEpICNhZGRpdGlvbmFsIGdyaWQgZnVuY3Rpb25hbGl0eQ0KbGlicmFyeShqYW5pdG9yKSAjb25lIGZ1bmN0aW9uIHVzZWQgdG8gY2xlYW4gdHJhbnNwb3NlZCBkYXRhIHNldA0KbGlicmFyeShnZ3B1YnIpICNmb3IgcXEgcGxvdA0KbGlicmFyeSh0YWJsZUhUTUwpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpgYGANCg0KVGhlIFwjIGNvbW1lbnRzIGdlbmVyYWxseSBleHBsYWluIHdoYXQgYWRkaXRpb25hbCBmdW5jdGlvbmFsaXR5IGVhY2ggbGlicmFyeSBhZGRzIHRvIFINCg0KIyMjIExvYWQgaW4gRGF0YQ0KDQpBbGwgZGF0YSBpcyBkb3dubG9hZGVkIGZyb20gRmFuIEdyYXBocy4gRnJvbSB0aGlzIFtsb2NhdGlvbl0oaHR0cHM6Ly93d3cuZmFuZ3JhcGhzLmNvbS9sZWFkZXJzLmFzcHg/cG9zPWFsbCZzdGF0cz1iYXQmbGc9YWxsJnF1YWw9eSZ0eXBlPTgmc2Vhc29uPTIwMjEmbW9udGg9MCZzZWFzb24xPTIwMjEmaW5kPTApLiBUaGUgZGF0YSBpcyBhbHNvIGF2YWlsYWJsZSBvbiBteSBHaXRodWIgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9kaXNzaXBhdGlvbi9GYW50YXN5LUJhc2ViYWxsKS4gVGhlcmUgYXJlIHBsYXllciBsZXZlbCBhbmQgdGVhbSBkYXRhIHNldHMNCg0KYGBge3IgZGF0YSByZWFkLWluLCByZXN1bHRzPSAnaGlkZScsbWVzc2FnZT1GQUxTRX0NCg0KI2RhdGEgcmVhZC1pbg0KQmF0dGVyX2RhdGEgPC0gcmVhZF9jc3YoIkZhbkdyYXBocyBMZWFkZXJib2FyZF9IaXR0aW5nNTBQQS5jc3YiKQ0KDQojVGVhbSBkYXRhc2V0cw0KRkRHX1RlYW0gPSByZWFkX2NzdigiRmFuR3JhcGhzIExlYWRlcmJvYXJkX1RlYW0uY3N2IikNCg0KDQojQ3JlYXRlIGEgcHJlZml4IGZvciBhbGwgdGVhbSBzdGF0cyB0aGF0IHN0YXJ0cyB3aXRoIFRfDQpGREdfVGVhbTIgPC0gRkRHX1RlYW0gJT4lIA0KICByZW5hbWVfd2l0aCggfiBwYXN0ZTAoIlRfIiwgLngpKQ0KDQpgYGANCg0KIyMjIENoZWNraW5nIFRlYW0gRGF0YQ0KDQpgc3RyYCBnaXZlIGluZm9ybWF0aW9uIGFib3V0IGFuIG9iamVjdCwgd2hpbGUgYHNraW1gIHByb3ZpZGVzIGEgY3VzdG9taXphYmxlIHN1bW1hcnkgIA0KDQpgYGB7ciBjaGVja2luZyB0ZWFtIGRhdGF9DQoNCiNPdXRwdXQgbm90IHNob3duIGZvciBzcGFjZQ0KI3N0cihGREdfVGVhbTIpDQoNCnNraW0oRkRHX1RlYW0yKSAlPiUgIA0KICB0aWJibGU6OmFzX3RpYmJsZSgpDQoNCg0KYGBgDQoqKiogIA0KDQojIyBVbmRlcnN0YW5kaW5nIHRoZSBEYXRhc2V0DQoNCiMjIyBFeHBsb3JpbmcgdGhlIGRhdGFzZXQNCg0KYHNraW1gIGxldCdzIHVzIHNlZSBob3cgdGhlIGRhdGEgd2FzIGltcG9ydGVkIGludG8gUi4gRG9jdW1lbnRhdGlvbiBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9za2ltci92aWduZXR0ZXMvc2tpbXIuaHRtbCkNCg0KYGBge3J9DQoNCiNGdWxsIERhdGFzZXQgZGltZW5zaW9ucw0KDQpza2ltcjo6c2tpbShCYXR0ZXJfZGF0YSkgJT4lIA0KICB0aWJibGU6OmFzX3RpYmJsZSgpICU+JSANCiAgc2VsZWN0KHNraW1fdHlwZSxza2ltX3ZhcmlhYmxlLGNvbXBsZXRlX3JhdGUpICU+JSANCiAgZmlsdGVyKGNvbXBsZXRlX3JhdGUgPjAuMzApICMyODggVmFyaWFibGVzDQoNCiNza2ltX3R5cGUgLSBjaGFyYWN0ZXIgb3IgbnVtZXJpYw0KI3NraW1fdmFyaWFibGUgLSBuYW1lIG9mIHZhcmlhYmxlDQojY29tcGxldGVfcmF0ZSAtICUgb2YgZGF0YSB0aGF0IGlzIG5vdCBtaXNzaW5nDQojZmlsdGVyIC0gb25seSBrZWVwIHZhcmlhYmxlcyB0aGF0IGhhdmUgMzAlIG9mIGRhdGEgcG9wdWxhdGVkDQpgYGANCg0KQWRkaXRpb25hbGx5IGxldCdzIGxvb2sgYXQgaG93IHZhcmlhYmxlcyB2YXJ5IGJ5IHllYXIgdG8gc2VlIGlmIHRoZXJlIGFyZSBhbnkgZGlzY3JlcGFuY2llcyB0aGVyZSAgDQoNCmBgYHtyfQ0KDQojSXQgbG9va3MgbGlrZSBvbmUgeWVhciwgdGhlcmUgd2VyZSBmZXdlciBnYW1lcyBwbGF5ZWQsIGFuZCB0aGVyZSBpcyBhIGNsZWFyIGRyb3Agb2ZmIGluIGhvbWUgcnVucw0KQmF0dGVyX2RhdGFfZGlzdCA9DQpCYXR0ZXJfZGF0YSAlPiUgDQogZ3JvdXBfYnkoU2Vhc29uKSAlPiUgDQogIHN1bW1hcml6ZSAoR2FtZXNfcGxheWVkID0gbWF4KEcpLA0KICAgICAgICAgICAgIEF2Z19IUj0gbWVhbihIUikNCiAgICAgICAgICAgICApDQpCYXR0ZXJfZGF0YV9kaXN0DQoNCmdncGxvdChCYXR0ZXJfZGF0YV9kaXN0LCBhZXMoU2Vhc29uLCBBdmdfSFIpKSArDQogIGdlb21fY29sKCkrDQogIGdndGl0bGUoIkF2ZXJhZ2UgSG9tZSBSdW5zIGJ5IFllYXIiKSsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSxzaXplID0gMjIsY29sb3IgPSJzdGVlbCBibHVlIikpDQoNCg0KDQpgYGANCg0KKioqICANCg0KIyMgQ2xlYW5pbmcgYW5kIENyZWF0aW5nIEluaXRpYWwgRGF0YXNldCBmb3IgTW9kZWwNCg0KV2hhdCBhcmUgc29tZSBpc3N1ZXMgd2l0aCB0aGUgZGF0YT8NCg0KMS4gIE1hbnkgb2YgVmFyaWFibGVzLCBzdWNoIGFzIEslLCBhcmUgYmVpbmcgcmVhZCBpbiBhcyBjaGFyYWN0ZXJzDQoNCiAgICAtICAgT25seSBUZWFtIGFuZCBQbGF5ZXIgTmFtZSBzaG91bGQgYmUgY2hhcmFjdGVycw0KDQoyLiAgVGhlcmUgaXMgc3BvdHR5IGRhdGEgY292ZXJhZ2UgaW4gc29tZSBvZiB0aGUgdmFyaWFibGVzIChcflZhcmlhYmxlcyBoYXZlIGxlc3MgdGhhbiAzMCUgQ292ZXJhZ2UpDQoNCjMuICAyMDIwIERhdGEgb25seSBpbmNsdWRlcyA2MCBnYW1lcyB3b3J0aCBvZiBkYXRhDQoNCiAgICAtICAgVGhpcyB3YXMgYSBzZWFzb24gc2hvcnRlbmVkIGR1ZSB0byBDb3ZpZC0xOQ0KDQo0LiAgVGVhbSBEYXRhIG5lZWRzIHRvIGJlIGFwcGVuZGVkIHRvIEJhdHRlciBEYXRhIGJ5IFRlYW0gTmFtZSANCg0KKioqICANCg0KIyMjIENsZWFubHkgQ2hhbmdpbmcgYWxsIFZhcmlhYmxlcyB0aGF0IGFyZSBjaGFyYWN0ZXJzIHRvIG51bWVyaWMuICANClRoZXJlIGFyZSBzZXZlcmFsIHdheXMgdG8gZG8gdGhpcywgd2Ugd2lsbCBpZGVudGlmeSB0aGUgdmFyaWFibGVzIHdlIHdhbnQgdG8gY2hhbmdlIHRoYXQgYXJlIG1pcy1pZGVudGlmaWVkLiBgcGFyc2VfbnVtYmVyYCBjYW4gYmUgdXNlZCB0byBwdWxsIG51bWJlcnMgZnJvbSB0aGVzZSB2YXJpYWJsZXMuIEFkZGl0aW9uYWwgd2F5cyB0byB0YWNrbGUgdGhpcyBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzgzMjkwNTkvaG93LXRvLWNvbnZlcnQtY2hhcmFjdGVyLW9mLXBlcmNlbnRhZ2UtaW50by1udW1lcmljLWluLXIpDQoNCmBgYHtyfQ0KDQojU2VsZWN0IENvbHVtbiBuYW1lcyB0aGF0IGFyZSBjaGFyYWN0ZXJzIGJ1dCBub3QgVGVhbSBvciBOYW1lLCBUaGVzZSBzaG91bGQgYmUgcGVyY2VudGFnZXMNCkJhdHRlcl9kYXRhX2NoYXJzX3RvX2NvbnZlcnQgPC0gQmF0dGVyX2RhdGEgJT4lIA0KICBzZWxlY3RfaWYoaXMuY2hhcmFjdGVyKSU+JSBzZWxlY3QoLVRlYW0sLU5hbWUpICU+JSANCiAgbXV0YXRlX2FsbCAoZnVuY3Rpb24oeCkgYXMubnVtZXJpYyhyZWFkcjo6cGFyc2VfbnVtYmVyKHgpKS8xMDApDQojTm90ZSA6IFRoZXJlIGFyZSBhZGRpdGlvbmFsIHdheXMgdG8gZG8gdGhpcywgdGhpcyBpcyBqdXN0IG9uZSBzb2x1dGlvbg0KDQoNCiNXZSBjYW4gZXhjbHVkZSB0aGUgdmFyaWFibGVzIHdlIGNvbnZlcnRlZCBhbmQgcmVpbnRyb2R1Y2UgdGhlbQ0KQmF0dGVyX2RhdGFfbnVtIDwtIEJhdHRlcl9kYXRhICU+JSBzZWxlY3QoLWNvbG5hbWVzKEJhdHRlcl9kYXRhX2NoYXJzX3RvX2NvbnZlcnQpKQ0KDQpCYXR0ZXJfZGF0YTIgPSBjYmluZChCYXR0ZXJfZGF0YV9udW0sQmF0dGVyX2RhdGFfY2hhcnNfdG9fY29udmVydCkgJT4lIA0KICBzZWxlY3QgKGNvbG5hbWVzKEJhdHRlcl9kYXRhKSkgJT4lICAjcHJlc2VydmUgb3JpZ2luYWwgb3JkZXIgDQogIGRwbHlyOjpyZW5hbWUoZmx5YmFsbF9wZXJjID0gYEZCJS4uLjQ2YCxmYXN0YmFsbF9wZXJjID0gYEZCJS4uLjczYCkgI3JlbmFtZSB0d28gYW1iaWd1b3VzIGNvbHVtbnMNCiAgDQpza2ltKEJhdHRlcl9kYXRhMikgJT4lIA0KICBhc190aWJibGUoKSAlPiUgDQogIGdyb3VwX2J5KHNraW1fdHlwZSkgJT4lIA0KICBjb3VudCgpDQoNCg0KI0xvZ2ljYWwgdmFyaWFibGVzIGFyZSBSJ3MgYmVzdCBndWVzcywgaW4gb3VyIGNhc2UgdGhleSBhcmUgYWxsIE5BJ3MgYW5kIHdpbGwgYmUgcmVtb3ZlZA0KDQpgYGANCg0KVGhlIHNhbWUgY2FuIGJlIGRvbmUgZm9yIHRoZSBUZWFtIERhdGEgdGhhdCBpcyBsb2FkZWQgIA0KDQoNCmBgYHtyfQ0KDQojU2VsZWN0IENvbHVtbiBuYW1lcyB0aGF0IGFyZSBjaGFyYWN0ZXJzIGJ1dCBub3QgVGVhbSBvciBOYW1lLCBUaGVzZSBzaG91bGQgYmUgcGVyY2VudGFnZXMNCkZER19UZWFtMl9jaGFyc190b19jb252ZXJ0IDwtIEZER19UZWFtMiAlPiUgDQogIHNlbGVjdF9pZihpcy5jaGFyYWN0ZXIpJT4lIHNlbGVjdCgtVF9UZWFtKSAlPiUgDQogIG11dGF0ZV9hbGwgKGZ1bmN0aW9uKHgpIGFzLm51bWVyaWMocmVhZHI6OnBhcnNlX251bWJlcih4KSkvMTAwKQ0KI0tlZXAgaW4gbWluZCwgcGFyc2UgbnVtYmVyIG1heSBtYWtlIGFjdHVhbCBjaGFyYWN0ZXJzIGludG8gbnVtZXJpY2FsIHZhcmlhYmxlcyBzbyBjYXJlZnVsbHkgY2hlY2sgeW91ciBkYXRhIGJlZm9yZSB1c2luZw0KDQojV2UgY2FuIGV4Y2x1ZGUgdGhlIHZhcmlhYmxlcyB3ZSBjb252ZXJ0ZWQgYW5kIHJlaW50cm9kdWNlIHRoZW0NCkZER19UZWFtMl9udW0gPC0gRkRHX1RlYW0yICU+JSBzZWxlY3QoLWNvbG5hbWVzKEZER19UZWFtMl9jaGFyc190b19jb252ZXJ0KSkNCg0KRkRHX1RlYW0zID0gY2JpbmQoRkRHX1RlYW0yX251bSxGREdfVGVhbTJfY2hhcnNfdG9fY29udmVydCkgJT4lIA0KICBzZWxlY3QgKGNvbG5hbWVzKEZER19UZWFtMikpICU+JSAgI3ByZXNlcnZlIG9yaWdpbmFsIG9yZGVyDQpkcGx5cjo6cmVuYW1lKFRfZmx5YmFsbF9wZXJjID0gYFRfRkIlLi4uNDVgLFRfZmFzdGJhbGxfcGVyYyA9IGBUX0ZCJS4uLjcyYCkgDQoNCnNraW0oRkRHX1RlYW0zKSAlPiUgDQogIGFzX3RpYmJsZSgpICU+JSANCiAgZ3JvdXBfYnkoc2tpbV90eXBlKSAlPiUgDQogIGNvdW50KCkNCg0KDQoNCmBgYA0KKioqICANCg0KDQojIyMgRmlsdGVyaW5nIERhdGEgd2l0aCBMb3cgQ292ZXJhZ2UgICAgDQpJIGNob29zZSAzMCUgY292ZXJhZ2Ugb2YgZGF0YSBuZWNlc3NhcnkgYnV0IHRoaXMgY2FuIGJlIGFkanVzdGVkIHVwIG9yIGRvd24uIFRoaXMgd2lsbCBhbHNvIGdldCByaWQgb2YgY29sdW1ucyB0aGF0IGFyZSBhbGwgYE5BYC4gIA0KYGBge3J9DQoNCiMgS2VlcCB2YXJpYWJsZXMgd2l0aCBlbm91Z2ggdmFsdWVzIChOZWVkIDMwJSBkYXRhIGNvdmVyYWdlIHJhdGUgaGVyZSkNClBsYXllcl9jb2xzX3RvX2tlZXAgPQ0Kc2tpbShCYXR0ZXJfZGF0YTIpICU+JSANCiAgZHBseXI6OnNlbGVjdChza2ltX3R5cGUsIHNraW1fdmFyaWFibGUsIGNvbXBsZXRlX3JhdGUpICU+JSANCiAgZmlsdGVyIChjb21wbGV0ZV9yYXRlID4gMC4zMCkNCg0KI1RyYW5zcG9zZSBSb3dzIHRvIGdldCBjb2x1bW4gbmFtZXMgYXMgc2tpbSBtZWx0cyB0aGUgZGF0YQ0KUGxheWVyX2NvbHNfdG9fa2VlcF90cmFuc3Bvc2UgPSB0KFBsYXllcl9jb2xzX3RvX2tlZXApIA0KDQojZXh0cmFjdCB0aGUgY29sbmFtZXMgd2Ugd291bGQgbGlrZSB0byBrZWVwDQpQbGF5ZXJfY29sc190b19rZWVwID0gY29sbmFtZXMoamFuaXRvcjo6cm93X3RvX25hbWVzKFBsYXllcl9jb2xzX3RvX2tlZXBfdHJhbnNwb3NlLHJvd19udW1iZXIgPSAyKSkNCg0KI09ubHkga2VlcCB0aGUgY29sdW1ucyBkZXNpZ25hdGVkIHRvIGhhdmUgb3ZlciAzMCUgb2YgdGhlaXIgZGF0YSBwb3B1bGF0ZWQgb3IgZ3JlYXRlcg0KQmF0dGVyX2RhdGEzID0gQmF0dGVyX2RhdGEyICU+JSANCiAgc2VsZWN0KG9uZV9vZihQbGF5ZXJfY29sc190b19rZWVwKSkgDQoNCg0KYGBgDQoNCg0KKlJlcGVhdCB0aGUgcHJvY2VzcyBmb3IgVGVhbSBWYXJpYWJsZXMqDQpgYGB7cn0NClRlYW1fY29sc190b19rZWVwID0NCnNraW0oRkRHX1RlYW0zKSAlPiUgDQogIGRwbHlyOjpzZWxlY3Qoc2tpbV90eXBlLCBza2ltX3ZhcmlhYmxlLCBjb21wbGV0ZV9yYXRlKSAlPiUgDQogIGZpbHRlciAoY29tcGxldGVfcmF0ZSA+IDAuMzApDQoNCg0KI1RyYW5zcG9zZSBSb3dzIHRvIGdldCBjb2x1bW4gbmFtZXMgYXMgc2tpbSBtZWx0cyB0aGUgZGF0YQ0KVGVhbV9jb2xzX3RvX2tlZXBfdHJhbnNwb3NlID0gdChUZWFtX2NvbHNfdG9fa2VlcCkgDQoNCiNleHRyYWN0IHRoZSBjb2xuYW1lcyB3ZSB3b3VsZCBsaWtlIHRvIGtlZXANClRlYW1fY29sc190b19rZWVwID0gY29sbmFtZXMoamFuaXRvcjo6cm93X3RvX25hbWVzKFRlYW1fY29sc190b19rZWVwX3RyYW5zcG9zZSxyb3dfbnVtYmVyID0gMikpDQoNCiNPbmx5IGtlZXAgdGhlIGNvbHVtbnMgZGVzaWduYXRlZCB0byBoYXZlIG92ZXIgMzAlIG9mIHRoZWlyIGRhdGEgcG9wdWxhdGVkIG9yIGdyZWF0ZXINCkZER19UZWFtNCA9IEZER19UZWFtMyAlPiUgDQogIHNlbGVjdChvbmVfb2YoVGVhbV9jb2xzX3RvX2tlZXApKSANCg0KDQoNCmBgYA0KDQoNCg0KKioqICANCg0KIyMjIENyZWF0aW5nIFZhcmlhYmxlcyBOb3JtYWxpemVkIGJ5IFllYXIgIA0KU29tZSBWYXJpYWJsZXMgd2lsbCBuZWVkIHRvIGJlIG5vcm1hbGl6ZWQgYnkgUGxhdGUgQXBwZWFyYW5jZXMgKFBBKSBpZiB0aGV5IGFyZW4ndCBhIHBlcmNlbnRhZ2UgYWxyZWFkeS4gUmVtYWluaW5nIFZhcmlhYmxlcyBhcmUgcGVyY2VudGFnZXMgb3IgaW5kaWNpZXMgc28gd2lsbCBub3QgbmVlZCB0byBiZSB0cmFuc2Zvcm1lZA0KDQpgYGB7cn0NCg0KQmF0dGVyX2RhdGE0ID0gQmF0dGVyX2RhdGEzICU+JSANCiAgbXV0YXRlKCAjY3JlYXRlIG5ldyB2YXJpYWJsZXMgYmFzZWQgb24gZXhpc3RpbmcgdmFyaWFibGVzDQogICAgSF9QQSA9IEgvUEEsDQogICAgeDFCX1BBID0gYDFCYC9QQSwgI25vdGU6IFIgY2FuJ3QgaGF2ZSB2YXJpYWJsZXMgc3RhcnQgd2l0aCBhIG51bWJlcg0KICAgIHgyYl9QQSA9IGAyQmAvUEEsDQogICAgeDNiX1BBID0gYDNCYC9QQSwNCiAgICBIUl9QQSA9IEhSL1BBLA0KICAgIFJfUEEgPSBSL1BBLA0KICAgIFJCSV9QQSA9IFJCSS9QQSwNCiAgICBCQl9QQSA9IEJCL1BBLA0KICAgIElCQl9QQSA9IElCQi9QQSwNCiAgICBTT19QQT1TTy9QQSwNCiAgICBIQlBfUEE9SEJQL1BBLA0KICAgIFNGX1BBPVNGL1BBLA0KICAgIFNIX1BBPVNIL1BBLA0KICAgIEdEUF9QQT0gR0RQL1BBLCNncm91bmQgaW50byBkb3VibGUgcGxheQ0KICAgIFNCX1BBPVNCL1BBLA0KICAgIENTX1BBPUNTL1BBLA0KICAgIEdCX1BBID0gR0IvUEEsICAgI0dyb3VuZGJhbGxzDQogICAgRkJfUEEgPSAgRkIvUEEsICAjRmx5QmFsbHMNCiAgICBMRF9QQSA9IExEL1BBLCAgICNMaW5lRHJpdmVzDQogICAgSUZGQl9QQSA9IElGRkIvUEEsICAjSW5maWVsZCBGbHkgYmFsbHMNCiAgICBQaXRjaGVzX1BBPSBQaXRjaGVzL1BBLA0KICAgIEJhbGxzX1BBPSBCYWxscy9QQSwNCiAgICBTdHJpa2VzX1BBPSBTdHJpa2VzL1BBLA0KICAgIElGSF9QQT0gSUZIL1BBLA0KICAgIEJVX1BBPSBCVS9QQSwNCiAgICBCVUhfUEE9IEJVSC9QQSwNCiAgICBQSF9QQT0gUEgvUEEsDQogICAgQmFycmVsc19QQT0gQmFycmVscy9QQSwNCiAgICBIYXJkSGl0c19QQT0gSGFyZEhpdC9QQQ0KICApICU+JSBzZWxlY3QoLShIOkNTKSwtKEdCOkJVSCksLVBILC1CYXJyZWxzLC1IYXJkSGl0LC1FdmVudHMpICNEcm9wIHRoZSBvbGQgdmFyaWFibGVzDQoNCiNza2ltKEJhdHRlcl9kYXRhNCkgJT4lIGFzX3RpYmJsZSgpDQoNCg0KYGBgDQoNCipSZXBlYXQgdGhlIHByb2Nlc3MgZm9yIFRlYW0gVmFyaWFibGVzKg0KYGBge3J9DQoNCkZER19UZWFtNSA9IEZER19UZWFtNCAlPiUgDQogIG11dGF0ZSggI2NyZWF0ZSBuZXcgdmFyaWFibGVzIGJhc2VkIG9uIGV4aXN0aW5nIHZhcmlhYmxlcw0KICAgIFRfSF9UX1BBID0gVF9IL1RfUEEsDQogICAgVF94MUJfVF9QQSA9IFRfMUIvVF9QQSwgI25vdGU6IFIgY2FuJ3QgaGF2ZSB2YXJpYWJsZXMgc3RhcnQgd2l0aCBhIG51bWJlcg0KICAgIFRfeDJiX1RfUEEgPSBUXzJCL1RfUEEsDQogICAgVF94M2JfVF9QQSA9IFRfM0IvVF9QQSwNCiAgICBUX0hSX1RfUEEgPSBUX0hSL1RfUEEsDQogICAgVF9SX1RfUEEgPSBUX1IvVF9QQSwNCiAgICBUX1JCSV9UX1BBID0gVF9SQkkvVF9QQSwNCiAgICBUX0JCX1RfUEEgPSBUX0JCL1RfUEEsDQogICAgVF9JQkJfVF9QQSA9IFRfSUJCL1RfUEEsDQogICAgVF9TT19UX1BBPVRfU08vVF9QQSwNCiAgICBUX0hCUF9UX1BBPVRfSEJQL1RfUEEsDQogICAgVF9TRl9UX1BBPVRfU0YvVF9QQSwNCiAgICBUX1NIX1RfUEE9VF9TSC9UX1BBLA0KICAgIFRfR0RQX1RfUEE9IFRfR0RQL1RfUEEsI2dyb3VuZCBpbnRvIGRvdWJsZSBwbGF5DQogICAgVF9TQl9UX1BBPVRfU0IvVF9QQSwNCiAgICBUX0NTX1RfUEE9VF9DUy9UX1BBLA0KICAgIFRfR0JfVF9QQSA9IFRfR0IvVF9QQSwgICAjR3JvdW5kYmFsbHMNCiAgICBUX0ZCX1RfUEEgPSAgVF9GQi9UX1BBLCAgI0ZseUJhbGxzDQogICAgVF9MRF9UX1BBID0gVF9MRC9UX1BBLCAgICNMaW5lRHJpdmVzDQogICAgVF9JRkZCX1RfUEEgPSBUX0lGRkIvVF9QQSwgICNJbmZpZWxkIEZseSBiYWxscw0KICAgIFRfUGl0Y2hlc19UX1BBPSBUX1BpdGNoZXMvVF9QQSwNCiAgICBUX0JhbGxzX1RfUEE9IFRfQmFsbHMvVF9QQSwNCiAgICBUX1N0cmlrZXNfVF9QQT0gVF9TdHJpa2VzL1RfUEEsDQogICAgVF9JRkhfVF9QQT0gVF9JRkgvVF9QQSwNCiAgICBUX0JVX1RfUEE9IFRfQlUvVF9QQSwNCiAgICBUX0JVSF9UX1BBPSBUX0JVSC9UX1BBLA0KICAgIFRfUEhfVF9QQT0gVF9QSC9UX1BBLA0KICAgIFRfQmFycmVsc19UX1BBPSBUX0JhcnJlbHMvVF9QQSwNCiAgICBUX0hhcmRIaXRzX1RfUEE9IFRfSGFyZEhpdC9UX1BBDQogICkgJT4lIHNlbGVjdCgtKFRfSDpUX0NTKSwtKFRfR0I6VF9CVUgpLC1UX1BILC1UX0JhcnJlbHMsLVRfSGFyZEhpdCwtVF9FdmVudHMpICNEcm9wIHRoZSBvbGQgdmFyaWFibGVzDQoNCg0KI3NraW0oRkRHX1RlYW01KSAlPiUgYXNfdGliYmxlKCkNCg0KDQpgYGANCg0KKioqICANCg0KIyMjIENyZWF0aW5nIExhZ2dlZCBWYXJpYWJsZXMgIA0KVGhlcmUgYXJlIHNldmVyYWwgd2F5cyB0byBsYWcgYSBkYXRhc2V0ICoqQlkgR1JPVVAqKi4gICAgDQoqIGBEcGx5cmAgd2F5IGlzIFtoZXJlLl0oaHR0cHM6Ly9zdGF0aXN0aWNzZ2xvYmUuY29tL2NyZWF0ZS1sYWdnZWQtdmFyaWFibGUtYnktZ3JvdXAtaW4tcikuICAgDQoqIFdoaWxlIGRhdGEudGFibGUgKHRoZSBtZXRob2QgdXNlZCBiZWxvdykgaXMgW2hlcmUuXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8yNjI5MTk4OC9ob3ctdG8tY3JlYXRlLWEtbGFnLXZhcmlhYmxlLXdpdGhpbi1lYWNoLWdyb3VwKSAgDQoNCmBgYHtyfQ0KI05vdGUgd2Ugd2lsbCBvbmx5IGJlIGxhZ2dpbmcgdGhlIHBsYXllciBsZXZlbCBkYXRhLCBhcyB0aGUgcHJldmlvdXMgeWVhcidzIHRlYW0gcGVyZm9ybWFuY2Ugc2hvdWxkbid0IGltcGFjdCBjdXJyZW50IHBlcmZvcm1hbmNlDQoNCg0KI09yZGVyIHRoZSBkYXRhc2V0IGJ5IGxhZyBjb2x1bW5zDQpCYXR0ZXJfZGF0YTUgPSAgYXJyYW5nZShCYXR0ZXJfZGF0YTQsIHBsYXllcmlkLFNlYXNvbikgI3BsYXllcmlkIGlzIHRoZSBGYW5ncmFwaCBpZCBhc3NpZ25lZCB0byBlYWNoIHBsYXllcg0KDQojIENvbnZlcnQgZGF0YWZyYW1lIHRvIGRhdGEudGFibGUgZm9ybWF0DQpEVF9iYXR0ZXIgPSBkYXRhLnRhYmxlKEJhdHRlcl9kYXRhNSkNCg0KI2Rlc2lnbmF0ZSBjb2x1bW5zIHRvIGxhZyAtIHdoaWNoIGlzIGFsbCBvZiB0aGVtDQpjb2xzMSA9IGNvbG5hbWVzKEJhdHRlcl9kYXRhNSkNCmFuc2NvbHMgPSBwYXN0ZSgibGFnIiwgY29sczEsIHNlcD0iXyIpDQpEVF9iYXR0ZXJbLCAoYW5zY29scykgOj0gZGF0YS50YWJsZTo6c2hpZnQoLlNELCAxLCBOQSwgImxhZyIpLGJ5ID0ncGxheWVyaWQnLCAuU0Rjb2xzPWNvbHMxXSAjQ3JlYXRlIDEgcGVyaW9kIGxhZ3MgYnkgeWVhcg0KDQpCYXR0ZXJfZGF0YTYgPSBhcy5kYXRhLmZyYW1lKERUX2JhdHRlcikgJT4lIHNlbGVjdCgtbGFnX3BsYXllcmlkLCAtbGFnX1RlYW0sIC1sYWdfU2Vhc29uLCAtbGFnX0FnZSwtbGFnX05hbWUpDQoNCm5jb2woQmF0dGVyX2RhdGE1KSAjMjg3IC0gbm8gbGFncw0KbmNvbChCYXR0ZXJfZGF0YTYpICM1NzQgLSBsYWdnZWQgZGF0YSB+ICgyODcgKiAyKS01DQoNCmBgYA0KDQoqKiogICAgDQoNCiMjIyBNZXJnaW5nIFRlYW0gYW5kIFBsYXllciBEYXRhICANCldlIGNhbiB1c2UgZWl0aGVyIHRoZSBgbWVyZ2VgIGZ1bmN0aW9uIG9yIHRoZSBTUUwgZnVuY3Rpb25hbGl0eSBwcm92aWRlZCBieSB0aGUgYHNxbGRmYCBwYWNrYWdlIHRvIGpvaW4gdGhlIGxhZ2dlZCBwbGF5ZXIgbGV2ZWwgZGF0YSB0byB0aGUgVGVhbSBsZXZlbCBkYXRhDQoNCmBgYHtyfQ0KDQpkZl9iYXR0aW5nX2luaXQgPSBzcWxkZigNCiAgIg0KICBzZWxlY3QgYS4qLCBiLioNCiAgZnJvbSBCYXR0ZXJfZGF0YTYgYQ0KICBsZWZ0IGpvaW4gRkRHX1RlYW01IGINCiAgb24gYS5UZWFtID0gYi5UX1RlYW0gYW5kIGEuU2Vhc29uID0gYi5UX1NlYXNvbg0KICANCiAgIg0KKSAgJT4lIHNlbGVjdCgtVF9UZWFtLC1UX1NlYXNvbixUX0FnZSxUX0csVF9BQikjIFVubmNlc3NhcnkgVGVhbSBWYXJpYWJsZXMNCg0KDQpucm93KGRmX2JhdHRpbmdfaW5pdCkgLSBucm93KEJhdHRlcl9kYXRhNikgI2NoZWNrIGlmIGFueSByb3dzIGFyZSBkdXBsaWNhdGVkDQoNCg0KYGBgDQoNCg0KKioqICANCg0KDQojIENyZWF0aW5nIFJhbmtpbmdzIGZvciBQbGF5ZXJzIEJhc2VkIE9uIFBlcmNlbnRpbGVzIHsudGFic2V0fSAgDQpXZSBjYW4gdXNlIFBlcmNlbnRpbGUgYmFzZWQgcmFua2luZyB0byBnZXQgcmFua2luZ3MgZm9yIHBsYXllcnMgZnJvbSB0aGUgMjAyMSBzZWFzb24uICANCg0KIyMgV29ydGggb2YgZWFjaCBzdGF0ICANCg0KIyMjIENhbGN1bGF0aW5nIHBhc3QgcGVyZm9ybWFuY2UgICANCg0KRWFjaCBwbGF5ZXIgZ29lcyBmcm9tIGEgMCUgdG8gMTAwJSBvbiBlYWNoIHBlcmNlbnRpbGUgc3RhdCB0aGF0IGlzIHVzZWQgZm9yIGNyZWF0aW5nIGEgc2NvcmluZyBvcHBvcnR1bml0eS4gQWxsIGRhdGEgaXMgYWxyZWFkeSBub3JtYWxpemVkIGJ5IHBsYXRlIGFwcGVhcmFuY2VzLCBidXQgbXVzdCBub3cgYmUgcmFua2VkIGZvciBlYWNoIHllYXIuICAgDQoNCg0KYGBge3J9DQoNCiNDYXRlZ29yaWVzIEkgaW5jbHVkZSBhcmU6DQojUnVucyAoUiksIEhvbWUgUnVucyAoSFIpLCBSdW5zIEJhdHRlZCBJbiAoUkJJKSwgU3RvbGVuIEJhc2VzIChTQiksIEJhdHRpbmcgQXZlcmFnZSAoQVZHKQ0KZGZfYmF0dGluZ19pbml0MiA9ICBkZl9iYXR0aW5nX2luaXQgJT4lDQojICBhcnJhbmdlKHBsYXllcl9pZCx5ZWFyKSAlPiUgDQogIGdyb3VwX2J5KFNlYXNvbikgJT4lIA0KICBtdXRhdGUoDQogICAgUnVuc19zaGFyZSA9IG9yZGVyKG9yZGVyKHJhbmsoUl9QQSx0aWVzLm1ldGhvZCA9ICdhdmVyYWdlJyksZGVjcmVhc2luZyA9IEZBTFNFKSkvbigpLA0KICAgICBIUl9zaGFyZSA9IG9yZGVyKG9yZGVyKHJhbmsoSFJfUEEsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBGQUxTRSkpL24oKSwNCiAgICAgUkJJX3NoYXJlID0gb3JkZXIob3JkZXIocmFuayhSQklfUEEsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBGQUxTRSkpL24oKSwNCiAgICAgU0Jfc2hhcmUgPSBvcmRlcihvcmRlcihyYW5rKFNCX1BBLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gRkFMU0UpKS9uKCksDQogICAgIEFWR19zaGFyZSA9IG9yZGVyKG9yZGVyKHJhbmsoQVZHLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gRkFMU0UpKS9uKCksDQogICAgT1BTX3NoYXJlID0gMCwNCiAgICBXb3J0aCA9IFJ1bnNfc2hhcmUrSFJfc2hhcmUrUkJJX3NoYXJlK1NCX3NoYXJlK0FWR19zaGFyZStPUFNfc2hhcmUNCiAgICApICU+JSANCiAgdW5ncm91cCgpIA0KDQpgYGANCg0KQ2hhcnQgb2YgdGhlIERpc3RyaWJ1dGlvbiBvZiBpbml0aWFsIHBlcmNlbnRpbGVzICANCkFzIHRoZSBjaGFydCBiZWxvdyBzaG93cywgdGhlIGRhdGEgaXMgcm91Z2hseSBub3JtYWwuDQpgYGB7cn0NCg0Kc2tld25lc3MoKGRmX2JhdHRpbmdfaW5pdDIkV29ydGgpKQ0KDQpnZ3Bsb3QyOjpxcGxvdChkZl9iYXR0aW5nX2luaXQyJFdvcnRoLCBtYWluPSJUb3RhbCBEYXRhc2V0IikgKyBnZW9tX2hpc3RvZ3JhbShjb2xvdXI9ImJsYWNrIiwgZmlsbD0iZ3JleSIpICsgdGhlbWVfYncoKQ0KDQptaW4oZGZfYmF0dGluZ19pbml0MiRXb3J0aCkNCg0KbWF4KGRmX2JhdHRpbmdfaW5pdDIkV29ydGgpDQoNCmdncHVicjo6Z2dxcXBsb3QoZGZfYmF0dGluZ19pbml0MiRXb3J0aCkNCg0Kc2hhcGlyby50ZXN0KGRmX2JhdHRpbmdfaW5pdDIkV29ydGgpDQpgYGANCg0KDQoqKiogIA0KDQoNCiMjIDIwMjEgUGxheWVyIFJhbmtpbmdzICANCiMjIyAyMDIxIFBsYXllciBSYW5raW5ncyAtIFRvcCBXb3J0aCBQbGF5ZXJzIHdpdGggT1BTDQpUb3RhbCBSYW5raW5ncyBmb3IgdGhlIHBsYXllcnMgKFVzaW5nIDV4NSBTY29yaW5nKSBjYW4gYmUgZm91bmQgW2hlcmVdKCkgIA0KYGBge3Isd2FybmluZz1GQUxTRX0NCg0Kb3B0aW9ucyhkaWdpdHM9MikNCg0KZGZfYmF0dGluZ19pbml0MjAyMSA9DQpkZl9iYXR0aW5nX2luaXQyICU+JSANCiAgZ3JvdXBfYnkoTmFtZSkgJT4lIA0KICBmaWx0ZXIoU2Vhc29uID09IDIwMjEpICU+JSANCiAgYXJyYW5nZShkZXNjKFdvcnRoKSkgJT4lIA0KICBzZWxlY3QoTmFtZSxSdW5zX3NoYXJlLEhSX3NoYXJlLFJCSV9zaGFyZSwgU0Jfc2hhcmUsT1BTX3NoYXJlLEFWR19zaGFyZSxXb3J0aCkNCg0KDQpkZl9iYXR0aW5nX2luaXQyMDIxICU+JQ0KICBmaWx0ZXIgKFdvcnRoPjMuOSkgJT4lIA0KICBrYmwoKSAlPiUgDQoga2FibGVfbWF0ZXJpYWwoYygic3RyaXBlZCIsICJob3ZlciIsImNvbmRlbnNlZCIsInJlc3BvbnNpdmUiKSxmdWxsX3dpZHRoID0gRixmaXhlZF90aGVhZCA9IFQpDQoNCmBgYA0KDQoNCioqKiAgDQoNCg0KIyBDcmVhdGluZyBNb2RlbCBGaWxlICAgDQojIyBBZGRpdGlvbmFsIERhdGEgUHJlcCAgDQojIyMgUmVtb3ZlIFZhcmlhYmxlcyB3aGljaCBhcmUgYmFzZWQgb2ZmIGN1cnJlbnQgaGl0dGluZyBudW1iZXJzICAgIA0KTm90IGFsbCB2YXJpYWJsZXMgY2FuIGJlIHVzZWQgZm9yIHByZWRpY3RpdmUgbW9kZWxpbmcuICAgDQpgYGB7cn0NCmRmX2JhdHRpbmdfaW5pdDMgPSBkZl9iYXR0aW5nX2luaXQyDQpgYGANCg0KTGFnIFNoYXJlIFZhcmlhYmxlcyB0byB1c2UgZm9yIHByZWRpY3RpdmUgbW9kZWxpbmcuIFRoZSB2YXJpYWJsZXMgdGhhdCB3ZSBjcmVhdGVkIGZvciB0aGUgV29ydGggbWV0cmljIG11c3QgYWxzbyBiZSByZW1vdmVkLiBUaGlzIHdpbGwgY3JlYXRlIHRoZSBmaW5hbCBkYXRhc2V0Lg0KDQpgYGB7cn0NCg0KI09yZGVyIHRoZSBkYXRhc2V0IGJ5IGxhZyBjb2x1bW5zDQpkZl9iYXR0aW5nX2luaXQ0ID0gIGFycmFuZ2UoZGZfYmF0dGluZ19pbml0MywgcGxheWVyaWQsU2Vhc29uKSAjcGxheWVyaWQgaXMgdGhlIEZhbmdyYXBoIGlkIGFzc2lnbmVkIHRvIGVhY2ggcGxheWVyDQoNCiMgQ29udmVydCBkYXRhZnJhbWUgdG8gZGF0YS50YWJsZSBmb3JtYXQNCkRUX2JhdHRlcjIgPSBkYXRhLnRhYmxlKGRmX2JhdHRpbmdfaW5pdDQpDQoNCiNkZXNpZ25hdGUgY29sdW1ucyB0byBsYWcgLSB3aGljaCBpcyBhbGwgb2YgdGhlbQ0KY29sczEgPSAoYygnUnVuc19zaGFyZScsJ0hSX3NoYXJlJywnUkJJX3NoYXJlJywgJ1NCX3NoYXJlJywnT1BTX3NoYXJlJywnQVZHX3NoYXJlJywnV29ydGgnKSkNCmFuc2NvbHMgPSBwYXN0ZSgibGFnIiwgY29sczEsIHNlcD0iXyIpDQpEVF9iYXR0ZXIyWywgKGFuc2NvbHMpIDo9IGRhdGEudGFibGU6OnNoaWZ0KC5TRCwgMSwgTkEsICJsYWciKSxieSA9J3BsYXllcmlkJywgLlNEY29scz1jb2xzMV0gI0NyZWF0ZSAxIHBlcmlvZCBsYWdzIGJ5IHllYXINCg0KZGZfYmF0dGluZ19maW5hbCA9IGFzLmRhdGEuZnJhbWUoRFRfYmF0dGVyMikgJT4lIA0KICBzZWxlY3QoLWMoUnVuc19zaGFyZSxIUl9zaGFyZSxSQklfc2hhcmUsIFNCX3NoYXJlLE9QU19zaGFyZSxBVkdfc2hhcmUpKSU+JSANCiAgc2VsZWN0KC1gSFIvRkJgLCAtRG9sLC1gQWdlIFJuZ2AsUEEsLShIX1BBOlBIX1BBKSwtTmFtZSkgIyU+JSANCiNzZWxlY3QoLVJfUEEsLUhSX1BBLC1SQklfUEEsLVNCX1BBLC1BVkcsLU9QUykNCg0KYGBgDQoNCg0KDQojIyMgQ3JlYXRpbmcgVHJhaW5pbmcvVGVzdCBTcGxpdCAgDQpXZSBzcGxpdCB0aGUgZGF0YSBpbnRvIFRyYWluaW5nIERhdGEgKHdoaWNoIGlzIHVzZWQgdG8gY3JlYXRlIHRoZSBtb2RlbCkgYW5kIHRlc3QgZGF0YSAod2hpY2ggaXMgdXNlZCB0byB2YWxpZGF0ZSB0aGUgbW9kZWwpICAgDQpgYGB7cn0NCg0Kc2V0LnNlZWQoMTU2NzQpICAjIEZvciByZXByb2R1Y2liaWxpdHkNCiMgQ3JlYXRlIGluZGV4IGZvciB0ZXN0aW5nIGFuZCB0cmFpbmluZyBkYXRhDQppblRyYWluIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IGRmX2JhdHRpbmdfZmluYWwkV29ydGgsIHAgPSAwLjgwLCBsaXN0ID0gRkFMU0UpDQojIHN1YnNldCBwaXRjaGluZyBkYXRhIGZvciB0cmFpbmluZw0KdHJfMjAyMSA8LSBkZl9iYXR0aW5nX2ZpbmFsW2luVHJhaW4sXQ0KIyBzdWJzZXQgdGhlIHJlc3QgdG8gdGVzdCBhbmQgdmFsaWRhdGUgdHJhaW5lZCBtb2RlbA0KdGVfMjAyMSA8LSBkZl9iYXR0aW5nX2ZpbmFsWy1pblRyYWluLF0NCg0KbnJvdyh0cl8yMDIxKS9ucm93KGRmX2JhdHRpbmdfZmluYWwpICNjaGVjayBpZiBzcGxpdCBpcyAwLjgNCg0KYGBgDQoNCiMjIyBUcmVhdCBNaXNzaW5nIERhdGEgYnkgSW1wdXRpbmcgTWVhbiBWYWx1ZSAgDQpWdHJlYXQgUGFja2FnZSBpbiBSIGlzIGV4Y2VsbGVudCBmb3IgdHJlYXRpbmcgZGF0YSBiZWZvcmUgdXNpbmcgZm9yIG1vZGVsaW5nLiBBZGRpdGlvbmFsIGRvY3VtZW50YXRpb24gY2FuIGJlIGZvdW5kIFtoZXJlLl0oaHR0cHM6Ly93aW52ZWN0b3IuZ2l0aHViLmlvL3Z0cmVhdC9pbmRleC5odG1sKQ0KYGBge3J9DQp0cmVhdF9wbGFuXzIwMjEgPC0gdnRyZWF0OjpkZXNpZ25UcmVhdG1lbnRzWigNCiAgZGZyYW1lID0gdHJfMjAyMSwgIyB0cmFpbmluZyBkYXRhDQogIHZhcmxpc3QgPSBjb2xuYW1lcyh0cl8yMDIxKSAlPiUgLlsuICE9ICJoaXR0aW5nX3Njb3JlMSJdLCAjIGlucHV0IHZhcmlhYmxlcyA9IGFsbCB0cmFpbmluZyBkYXRhIGNvbHVtbnMsIGV4Y2VwdCByYW5kb20NCiAgY29kZVJlc3RyaWN0aW9uID0gYygiY2xlYW4iLCAiaXNCQUQiLCAibGV2IiksICMgZGVyaXZlZCB2YXJpYWJsZXMgdHlwZXMgKGRyb3AgY2F0X1ApDQogIHZlcmJvc2UgPSBGQUxTRSkgIyBzdXBwcmVzcyBtZXNzYWdlcw0KDQojY2xlYW4gc3RhbmRzIGZvciBjbGVhbmVkIG51bWVyaWNhbCB2YXJpYWJsZSwgaXNCQUQgaW5kaWNhdGVzIHRoYXQgYSB2YWx1ZSByZXBsYWNlbWVudCBoYXMgb2NjdXJyZWQgKHdoaWNoIGluZGljYXRlcyBhIG1pc3NpbmcgdmFsdWUgaW4gdGhpcyBjYXNlKSwgYW5kIGxldiBpcyBhIGJpbmFyeSBpbmRpY2F0b3Igd2hldGhlciBhIHBhcnRpY3VsYXIgdmFsdWUgb2YgdGhhdCBjYXRlZ29yaWNhbCB2YXJpYWJsZSB3YXMgcHJlc2VudC4gIA0KDQojIyMjIENoZWNraW5nIFNjb3JlZnJhbWUNCg0Kc2NvcmVfZnJhbWUgPC0gdHJlYXRfcGxhbl8yMDIxJHNjb3JlRnJhbWUgJT4lIA0KICBzZWxlY3QodmFyTmFtZSwgb3JpZ05hbWUsIGNvZGUpDQoNCmhlYWQoc2NvcmVfZnJhbWUpDQoNCg0KdHJfdHJlYXRlZF8yMDIxIDwtIHZ0cmVhdDo6cHJlcGFyZSh0cmVhdF9wbGFuXzIwMjEsIHRyXzIwMjEpDQp0ZV90cmVhdGVkXzIwMjEgPC0gdnRyZWF0OjpwcmVwYXJlKHRyZWF0X3BsYW5fMjAyMSwgdGVfMjAyMSkNCg0KDQpUb3RhbF9kYXRhc2V0MV91bnRyZWF0ID0gYXMuZGF0YS5mcmFtZShEVF9iYXR0ZXIyKSAlPiUgc2VsZWN0KC1OYW1lKQ0KDQp0cmVhdF9wbGFuXzIwMjEgPC0gdnRyZWF0OjpkZXNpZ25UcmVhdG1lbnRzWigNCiAgZGZyYW1lID0gVG90YWxfZGF0YXNldDFfdW50cmVhdCwgIyB0cmFpbmluZyBkYXRhDQogIHZhcmxpc3QgPSBjb2xuYW1lcyhUb3RhbF9kYXRhc2V0MV91bnRyZWF0KSAlPiUgLlsuICE9ICJoaXR0aW5nX3Njb3JlMSJdLCAjIGlucHV0IHZhcmlhYmxlcyA9IGFsbCB0cmFpbmluZyBkYXRhIGNvbHVtbnMsIGV4Y2VwdCByYW5kb20NCiAgY29kZVJlc3RyaWN0aW9uID0gYygiY2xlYW4iLCAiaXNCQUQiLCAibGV2IiksICMgZGVyaXZlZCB2YXJpYWJsZXMgdHlwZXMgKGRyb3AgY2F0X1ApDQogIHZlcmJvc2UgPSBGQUxTRSkgIyBzdXBwcmVzcyBtZXNzYWdlcw0KDQoNCnRvdGFsX3RyZWF0ZWRfMjAyMV9oaXR0aW5nIDwtIHZ0cmVhdDo6cHJlcGFyZSh0cmVhdF9wbGFuXzIwMjEsIFRvdGFsX2RhdGFzZXQxX3VudHJlYXQpDQoNCg0KDQojdHJfdHJlYXRlZCA9IHRyDQojdGVfdHJlYXRlZCA9IHRlDQoNCmRpbSh0cl90cmVhdGVkXzIwMjEpICNub3RlIHRoZXJlIGFyZSBkdW1taWVzIGZvciBlYWNoIHBsYXllciBhbmQgdGVhbQ0KDQpgYGANCg0KDQoqKiogICAgDQoNCg0KIyMjIENoZWNrIERpc3RyaWJ1dGlvbiBvZiBUcmFpbmluZyBQb3B1bGF0aW9uICANClRoZSBwb3B1bGF0aW9uIHVzZWQgZm9yIFRyYWluaW5nIHNob3VsZCBiZSBpbmRpY2F0aXZlIG9mIFRvdGFsIFBvcHVsYXRpb24NCmBgYHtyfQ0KDQpnZ3Bsb3QyOjpxcGxvdCh0cl90cmVhdGVkXzIwMjEkV29ydGgsIG1haW49IlRyYWluaW5nIFNldCIpICsgZ2VvbV9oaXN0b2dyYW0oY29sb3VyPSJibGFjayIsIGZpbGw9ImdyZXkiKSArIHRoZW1lX2J3KCkNCg0Kc2tld25lc3ModHJfdHJlYXRlZF8yMDIxJFdvcnRoKSAjVGhlIHNrZXduZXNzIGlzIHRoZSBzYW1lIGFzIHRoZSBvdmVyYWxsDQoNCg0KYGBgDQoNCg0KIyBSdW5uaW5nIFhHYm9vc3QgTW9kZWwgey50YWJzZXR9IA0KVG8ga2VlcCB0aGluZ3Mgc2ltcGxlIHdpdGggbW9kZWxpbmcsIHdl4oCZbGwgdHVybiB0aGUgdHJhaW5pbmcgZGF0YSBpbnRvIHNpbXBsZSBpbnB1dCB2YXJpYWJsZXMgZm9yIGBjYXJldDo6dHJhaW5gLCBkcm9wcGluZyB0aGUgcmVzcG9uc2UgdmFyaWFibGUgYW5kIGNvbnZlcnRpbmcgdGhlIGRhdGEgZnJhbWUgdG8gYSBtYXRyaXguIERvY3VtZW50YXRpb24gZm9yIHRoaXMgYXBwcm9hY2ggdG8gWEdib29zdCBjYW4gYmUgZm91bmQgW2hlcmUuXShodHRwczovL3d3dy5rYWdnbGUuY29tL3BlbGtvamEvdmlzdWFsLXhnYm9vc3QtdHVuaW5nLXdpdGgtY2FyZXQpICAgIA0KDQojIyBUdW5pbmcgdGhlIE1vZGVsDQoNCiMjIyBJbml0aWFsIE5vbi1UdW5lZCBNb2RlbA0KQnJlYWsgdGhlIGRhdGEgc2V0IGludG8geCBhbmQgeSBpbnB1dHMgd2l0aCB4IGJlaW5nIGEgbWF0cml4ICANCmBgYHtyfQ0KaW5wdXRfeCA8LSBhcy5tYXRyaXgoKCh0cl90cmVhdGVkXzIwMjEpKSU+JQ0KICAgc2VsZWN0KC1Xb3J0aCkgJT4lICAgICAgICAgICAgICAgICAgICAgIA0KICAgc2VsZWN0KCFlbmRzX3dpdGggKCJfaXNCQUQiKSkpDQoNCmlucHV0X3kgPC0gdHJfdHJlYXRlZF8yMDIxJFdvcnRoDQoNCmBgYA0KDQpYR0Jvb3N0IHdpdGggRGVmYXVsdCBIeXBlcnBhcmFtZXRlcnMgICAgDQpUaGUgVmFyaWFibGUgSW1wb3J0YW5jZSAoYGNhcmV0Ojp2YXJJbXAoeGdiX2Jhc2VfMjAyMSwgc2NhbGUgPSBGICApYCkgZnJvbSB0aGUgY2FyZXQgcGFja2FnZSBzaG93cyB0aGUgY29udHJpYnV0aW9uIG9mIGVhY2ggdmFyaWFibGUgdG8gdGhlIGluaXRpYWwgbW9kZWwuIEFzIHlvdSBjYW4gc2VlIFNMR19wbHVzXyAoU0xHKykgdGFrZXMgdXAgbXVjaCBvZiB0aGUgaW1wb3J0YW5jZSBhcyBpdCBpcyBkZXJpdmVkIGZyb20gU0xHIChvbmUgb2YgdGhlIGtleSBjb250cmlidXRvcnMgdG8gV29ydGgpLiBUaGVzZSB0eXBlcyBvZiB2YXJpYWJsZXMgd2lsbCBiZSByZW1vdmVkIGR1cmluZyB2YXJpYWJsZSBzZWxlY3Rpb24gaW4gdGhlIG5leHQgc3RlcC4gIA0KKlhHQm9vc3QgZG9jdW1lbnRhdGlvbiBjYW4gYmUgZm91bmQgZm9yIG1vcmUgZ2VuZXJhbCBtb2RlbHMgW2hlcmUuXShodHRwczovL3d3dy5rYWdnbGUuY29tL2NvZGUvcnRhdG1hbi9tYWNoaW5lLWxlYXJuaW5nLXdpdGgteGdib29zdC1pbi1yL25vdGVib29rKSoNCg0KYGBge3J9DQoNCiNEZWZhdWx0cyBmb3IgeGdib29zdCBtb2RlbA0KZ3JpZF9kZWZhdWx0IDwtIGV4cGFuZC5ncmlkKA0KICBucm91bmRzID0gMTAwLA0KICBtYXhfZGVwdGggPSA2LA0KICBldGEgPSAwLjMsDQogIGdhbW1hID0gMCwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IDEsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSAxLA0KICBzdWJzYW1wbGUgPSAxDQopDQoNCiNUaGlzIGlzIGEgYmxhbmsgdHJhaW5fY29udHJvbCBzZXQsIHRoaXMgd2lsbCBiZSB1cGRhdGVkIGFmdGVyDQp0cmFpbl9jb250cm9sIDwtIGNhcmV0Ojp0cmFpbkNvbnRyb2woDQogIG1ldGhvZCA9ICJub25lIiwNCiAgdmVyYm9zZUl0ZXIgPSBGQUxTRSwgIyBubyB0cmFpbmluZyBsb2cNCiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUgIyBGQUxTRSBmb3IgcmVwcm9kdWNpYmxlIHJlc3VsdHMgDQopDQoNCnhnYl9iYXNlXzIwMjEgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gaW5wdXRfeCwNCiAgeSA9IGlucHV0X3ksDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gZ3JpZF9kZWZhdWx0LA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopDQoNCmNhcmV0Ojp2YXJJbXAoeGdiX2Jhc2VfMjAyMSwgc2NhbGUgPSBGICApDQoNCg0KDQoNCmBgYA0KDQojIyBGdXJ0aGVyIFZhcmlhYmxlIFNlbGVjdGlvbiAgDQojIyMgUmVtb3ZlIHJlZHVuZGFudCBhbmQgaGlnaGx5IGNvcnJlbGF0ZWQgdmFyaWFibGVzICANCg0KDQpTZWxlY3Rpb24gUmVtb3ZhbCBTdGVwIDE6IENoZWNrIGZvciBoaWdoIGNvcnJlbGF0aW9ucyAgDQpOb3JtYWxseSwgdGhpcyBzdGVwIGlzIGRvbmUgZWFybHksIGJ1dCB0aG9zZSBzdGVwcyB3ZXJlIHJlc2VydmVkIGZvciBwcmVwYXJpbmcgdGhlIGRhdGEgIA0KDQpgYGB7cn0NCg0KZGVwX2NvcjEgPC0gdChhcy5kYXRhLmZyYW1lKGNvcih0cl90cmVhdGVkXzIwMjFbICwgY29sbmFtZXModHJfdHJlYXRlZF8yMDIxKSAhPSAiV29ydGgiXSwNCiAgICAgICAgICAgICAgICB0cl90cmVhdGVkXzIwMjEkV29ydGgpKSkNCmRlcF9jb3IxIDwtDQphcy5kYXRhLmZyYW1lKHQoYXMuZGF0YS5mcmFtZShkZXBfY29yMSklPiUgDQogIHNlbGVjdCghc3RhcnRzX3dpdGgoImxhZyIpKSAlPiUgI3JlbW92ZSBsYWcgdmFyaWFibGVzDQogIHNlbGVjdCghY29udGFpbnMoIl9pc0JBRCIpKSkpIA0KDQpkZXBfY29yMSA8LSB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbihkZXBfY29yMSwiVkFSSUFCTEVTIiklPiUgI3JlbW92ZSBpbmRpY2F0b3JzIGZvciBtaXNzaW5nIGRhdGENCiAgZmlsdGVyKFYxID4gMC43MHxWMSA8IC0wLjUpDQoNCmRlcF9jb3IxDQoNCmRlcF9jb3IyIDwtIGNvbG5hbWVzKHJvd190b19uYW1lcyh0KGRlcF9jb3IxKSxyb3dfbnVtYmVyID0gMSkpDQoNCg0KDQpgYGANCkxldCdzIFJlbW92ZSB2YXJpYWJsZXMgd2l0aCBoaWdoIGNvcnJlbGF0aW9uIHRvIHdvcnRoIG1ldHJpYywgYW5kIG1ldHJpY3MgdGhhdCBhcmUgY2FsY3VsYXRlZCBhZnRlciBhIHBsYXllcidzIHBlcmZvcm1hbmNlIChzdWNoIGFzIFdBUikNCg0KYGBge3J9DQoNCmlucHV0X3ggPC0gYXMubWF0cml4KCgodHJfdHJlYXRlZF8yMDIxKSklPiUNCiAgIHNlbGVjdCgtV29ydGgpICU+JSAjUmVtb3ZlIGRlcGVuZGFudCB2YXJpYWJsZXMNCiAgICAgc2VsZWN0ICgtYWxsX29mKGRlcF9jb3IyKSwtUkFSLC1XQVIsLVdQQSwtU3BkLC1SZXAsLUJBQklQLC1EZWYsLUJBQklQX3BsdXNfLC1QQSwtQUIsLUcsLXdGQiwJLXdTTCwJLXdDVCwJLXdDSCwJLXdTRiwgLVhfcGx1c19XUEEsIC13U0IsIC1Cc1IsLVBvcywtVUJSICwgLXdDQiAjUmVtb3ZlIHJlZHVuZGFudCB2YXJpYWJsZXMgb3Igbm9uL3dlaWdodGVkIHZhcmlhYmxlcw0KKSAlPiUgICAgICANCnNlbGVjdCghZW5kc193aXRoICgiX2lzQkFEIikpKSAjaW5kaWNhdG9yIHZhcmlhYmxlIGZvciBtaXNzaW5nIGRhdGENCg0KaW5wdXRfeSA8LSB0cl90cmVhdGVkXzIwMjEkV29ydGgNCg0KDQoNCg0KDQpgYGANCg0KUnVuIHRoZSBtb2RlbCBvbiB0aGUgbmV3IGRhdGFzZXQgdG8gbWFrZSBzdXJlIHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlcyBsb29rIGZpbmUNCmBgYHtyfQ0KDQojTm90ZSBUcmFpbmluZyBwYXJhbWV0ZXJzIHdlcmUgc2V0IGluIGluaXRpYWwgbW9kZWwgc2V0IHVwDQp4Z2JfYmFzZV8yMDIxIDwtIGNhcmV0Ojp0cmFpbigNCiAgeCA9IGlucHV0X3gsDQogIHkgPSBpbnB1dF95LA0KICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICB0dW5lR3JpZCA9IGdyaWRfZGVmYXVsdCwNCiAgbWV0aG9kID0gInhnYlRyZWUiLA0KICB2ZXJib3NlID0gVFJVRQ0KKQ0KDQpjYXJldDo6dmFySW1wKHhnYl9iYXNlXzIwMjEsIHNjYWxlID0gRiAgKQ0KDQoNCmBgYA0KDQoNCiMjIE1vZGVsIHdpdGggbmV3IGRhdGEgIA0KDQojIyMgVHVuaW5nIEFsbCBIeXBlcnBhcmFtZXRlcnMNCkEgdHVuZSBncmlkIGFsbG93cyB1cyB0byB0ZXN0IGEgbGFyZ2UgYW1vdW50IG9mIGh5cGVyLXBhcmFtZXRlcnMgYW5kIGZpbmQgdGhlIG1vZGVsIHdpdGggdGhlIGxvd2VzdCBSTVNFIGZvciBwcmVkaWN0aW9ucy4gICANCkhvd2V2ZXIsIFRoZSBtb3JlIHZhbHVlcyB5b3Ugd2FudCB0byB0ZXN0IGFuZCB0aGUgZ3JlYXRlciB0aGUgYW1vdW50IG9mIENyb3NzLUZvbGQgVmFsaWRhdGlvbnMgKGBtZXRob2QgPSAiY3YiYCksIHRoZSBncmVhdGVyIHRoZSBjb21wdXRhdGlvbmFsIHRpbWUgaXQgd2lsbCB0YWtlLiBNb3JlIGluZm9ybWF0aW9uIG9uIHRoZSBzcGVjaWZpYyBwYXJhbWV0ZXJzIGNhbiBiZSBmb3VuZCBbaGVyZS5dKGh0dHBzOi8vd3d3LmhhY2tlcmVhcnRoLmNvbS9wcmFjdGljZS9tYWNoaW5lLWxlYXJuaW5nL21hY2hpbmUtbGVhcm5pbmctYWxnb3JpdGhtcy9iZWdpbm5lcnMtdHV0b3JpYWwtb24teGdib29zdC1wYXJhbWV0ZXItdHVuaW5nLXIvdHV0b3JpYWwvKQ0KDQpgYGB7cn0NCg0KIyBtYXhpbXVtIG51bWJlciBvZiB0cmVlcw0KbnJvdW5kcyA8LSAxMDAwDQoNCiMgbm90ZSB0byBzdGFydCBucm91bmRzIGZyb20gMjAwLCBhcyBzbWFsbGVyIGxlYXJuaW5nIHJhdGVzIHJlc3VsdCBpbiBlcnJvcnMgc28NCiMgYmlnIHdpdGggbG93ZXIgc3RhcnRpbmcgcG9pbnRzIHRoYXQgdGhleSdsbCBtZXNzIHRoZSBzY2FsZXMNCnR1bmVfZ3JpZCA8LSBleHBhbmQuZ3JpZCgNCiAgbnJvdW5kcyA9IHNlcShmcm9tID0gMTAwLCB0byA9IG5yb3VuZHMsIGJ5ID0gNTApLA0KICBldGEgPSBjKDAuMDEsIDAuMDI1LCAwLjA1LCAwLjEpLA0KICBtYXhfZGVwdGggPSBjKDIsIDQsIDYsIDgpLA0KICBnYW1tYSA9IDAsDQogIGNvbHNhbXBsZV9ieXRyZWUgPSAxLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0gMSwNCiAgc3Vic2FtcGxlID0gMQ0KKQ0KDQp0dW5lX2NvbnRyb2wgPC0gY2FyZXQ6OnRyYWluQ29udHJvbCgNCiAgbWV0aG9kID0gImN2IiwgIyBjcm9zcy12YWxpZGF0aW9uDQogIG51bWJlciA9IDUsICMgd2l0aCBuIGZvbGRzIA0KICAjIyBOb3RlIHRoaXMgd2FzICMgb3V0IGluIHRoZSBvcmlnaW5hbCBjb2RlDQogICNpbmRleCA9IGNyZWF0ZUZvbGRzKHRyX3RyZWF0ZWQkSWRfY2xlYW4pLCAjIGZpeCB0aGUgZm9sZHMNCiAgdmVyYm9zZUl0ZXIgPSBGQUxTRSwgIyBubyB0cmFpbmluZyBsb2cNCiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUgIyBGQUxTRSBmb3IgcmVwcm9kdWNpYmxlIHJlc3VsdHMgDQopDQoNCg0KDQpgYGANCg0KKlJ1bm5pbmcgdGhlIGluaXRpYWwgdHVuaW5nIG1vZGVsKiAgDQpgYGB7cn0NCiNOb3RlIEkgd2lsbCBiZSB0aW1pbmcgdGhlc2UgcnVucyB0byBnaXZlIGFuIGVzdGltYXRlIG9uIGhvdyBsb25nIHRoaXMgbW9kZWwgdGFrZXMgdG8gcnVuDQpzdGFydF90aW1lIDwtIFN5cy50aW1lKCkNCg0KeGdiX3R1bmVfMjAyMSA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94LA0KICB5ID0gaW5wdXRfeSwNCiAgdHJDb250cm9sID0gdHVuZV9jb250cm9sLA0KICB0dW5lR3JpZCA9IHR1bmVfZ3JpZCwNCiAgbWV0aG9kID0gInhnYlRyZWUiLA0KICB2ZXJib3NlID0gRkFMU0UNCiAgLHZlcmJvc2l0eSA9IDANCikNCg0KZW5kX3RpbWUgPC0gU3lzLnRpbWUoKQ0KDQplbmRfdGltZSAtIHN0YXJ0X3RpbWUNCg0KYGBgDQoNCipUdW5pbmcgUGxvdCBhbmQgVmFyaWFibGUgSW1wb3J0YW5jZSoNCmBgYHtyfQ0KdmFySW1wKHhnYl90dW5lXzIwMjEsIHNjYWxlID0gRiAgKSANCg0KDQojIGhlbHBlciBmdW5jdGlvbiBmb3IgdGhlIHBsb3RzDQp0dW5lcGxvdCA8LSBmdW5jdGlvbih4LCBwcm9icyA9IC45MCkgew0KICBnZ3Bsb3QoeCkgKw0KICAgIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYyhxdWFudGlsZSh4JHJlc3VsdHMkUk1TRSwgcHJvYnMgPSBwcm9icyksIG1pbih4JHJlc3VsdHMkUk1TRSkpKSArDQogICAgdGhlbWVfYncoKQ0KfQ0KDQp0dW5lcGxvdCh4Z2JfdHVuZV8yMDIxKQ0KYGBgDQoNCiMjIyBGaW5lIFR1bmluZyBNb2RlbCAgDQojIyMjIFNlY29uZCBUdW5pbmc6IE1heGltdW0gRGVwdGggYW5kIE1pbmltdW0gQ2hpbGQgV2VpZ2h0ICANCkFmdGVyIGZpeGluZyB0aGUgbGVhcm5pbmcgcmF0ZSB0byAwLjEgYW5kIHdl4oCZbGwgYWxzbyBzZXQgbWF4aW11bSBkZXB0aCB0byAzICstMSAob3IgKzIgaWYgbWF4X2RlcHRoID09IDIpIHRvIGV4cGVyaW1lbnQgYSBiaXQgYXJvdW5kIHRoZSBzdWdnZXN0ZWQgYmVzdCB0dW5lIGluIHByZXZpb3VzIHN0ZXAuIFRoZW4sIHdlbGwgZml4IG1heGltdW0gZGVwdGggYW5kIG1pbmltdW0gY2hpbGQgd2VpZ2gNCg0KYGBge3J9DQp0dW5lX2dyaWQyIDwtIGV4cGFuZC5ncmlkKA0KICBucm91bmRzID0gc2VxKGZyb20gPSA1MCwgdG8gPSBucm91bmRzLCBieSA9IDUwKSwNCiAgZXRhID0geGdiX3R1bmVfMjAyMSRiZXN0VHVuZSRldGEsDQogIG1heF9kZXB0aCA9IGlmZWxzZSh4Z2JfdHVuZV8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCA9PSAyLA0KICAgIGMoeGdiX3R1bmVfMjAyMSRiZXN0VHVuZSRtYXhfZGVwdGg6NCksDQogICAgeGdiX3R1bmVfMjAyMSRiZXN0VHVuZSRtYXhfZGVwdGggLSAxOnhnYl90dW5lXzIwMjEkYmVzdFR1bmUkbWF4X2RlcHRoICsgMSksDQogIGdhbW1hID0gMCwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IDEsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDIsIDMpLA0KICBzdWJzYW1wbGUgPSAxDQopDQoNCnhnYl90dW5lMl8yMDIxIDwtIGNhcmV0Ojp0cmFpbigNCiAgeCA9IGlucHV0X3gsDQogIHkgPSBpbnB1dF95LA0KICB0ckNvbnRyb2wgPSB0dW5lX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gdHVuZV9ncmlkMiwNCiAgbWV0aG9kID0gInhnYlRyZWUiLA0KICB2ZXJib3NlID0gVFJVRQ0KKQ0KDQp0dW5lcGxvdCh4Z2JfdHVuZTJfMjAyMSkNCg0KeGdiX3R1bmUyXzIwMjEkYmVzdFR1bmUNCg0KdmFySW1wKHhnYl90dW5lMl8yMDIxLCBzY2FsZSA9IEYgICkgDQpgYGANCg0KIyMjIyBUaGlyZCBUdW5pbmc6IENvbHVtbiBhbmQgUm93IFNhbXBsaW5nDQoNCmBgYHtyfQ0KDQp0dW5lX2dyaWQzIDwtIGV4cGFuZC5ncmlkKA0KICBucm91bmRzID0gc2VxKGZyb20gPSA1MCwgdG8gPSBucm91bmRzLCBieSA9IDUwKSwNCiAgZXRhID0geGdiX3R1bmVfMjAyMSRiZXN0VHVuZSRldGEsDQogIG1heF9kZXB0aCA9IHhnYl90dW5lMl8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCwNCiAgZ2FtbWEgPSAwLA0KICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjQsIDAuNiwgMC44LCAxLjApLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0geGdiX3R1bmUyXzIwMjEkYmVzdFR1bmUkbWluX2NoaWxkX3dlaWdodCwNCiAgc3Vic2FtcGxlID0gYygwLjUsIDAuNzUsIDEuMCkNCikNCg0KeGdiX3R1bmUzXzIwMjEgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gaW5wdXRfeCwNCiAgeSA9IGlucHV0X3ksDQogIHRyQ29udHJvbCA9IHR1bmVfY29udHJvbCwNCiAgdHVuZUdyaWQgPSB0dW5lX2dyaWQzLA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopDQoNCnR1bmVwbG90KHhnYl90dW5lM18yMDIxLCBwcm9icyA9IC45NSkNCg0KeGdiX3R1bmUzXzIwMjEkYmVzdFR1bmUNCg0KdmFySW1wKHhnYl90dW5lM18yMDIxLCBzY2FsZSA9IEYgICkgDQoNCmBgYA0KDQojIyMjIEZvdXJ0aCBUdW5pbmc6IEdhbW1hICANCk5leHQsIHdlIGFnYWluIHBpY2sgdGhlIGJlc3QgdmFsdWVzIGZyb20gcHJldmlvdXMgc3RlcCwgYW5kIG5vdyB3aWxsIHNlZSB3aGV0aGVyIGNoYW5naW5nIHRoZSBnYW1tYSBoYXMgYW55IGVmZmVjdCBvbiB0aGUgbW9kZWwgZml0Og0KYGBge3J9DQp0dW5lX2dyaWQ0IDwtIGV4cGFuZC5ncmlkKA0KICBucm91bmRzID0gc2VxKGZyb20gPSA1MCwgdG8gPSBucm91bmRzLCBieSA9IDUwKSwNCiAgZXRhID0geGdiX3R1bmVfMjAyMSRiZXN0VHVuZSRldGEsDQogIG1heF9kZXB0aCA9IHhnYl90dW5lMl8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCwNCiAgZ2FtbWEgPSBjKDAsIDAuMDUsMC4xLCAwLjIsMC40LCAwLjUsIDAuNywgMC45LCAxLjApLA0KICBjb2xzYW1wbGVfYnl0cmVlID0geGdiX3R1bmUzXzIwMjEkYmVzdFR1bmUkY29sc2FtcGxlX2J5dHJlZSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IHhnYl90dW5lMl8yMDIxJGJlc3RUdW5lJG1pbl9jaGlsZF93ZWlnaHQsDQogIHN1YnNhbXBsZSA9IHhnYl90dW5lM18yMDIxJGJlc3RUdW5lJHN1YnNhbXBsZQ0KKQ0KDQp4Z2JfdHVuZTRfMjAyMSA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94LA0KICB5ID0gaW5wdXRfeSwNCiAgdHJDb250cm9sID0gdHVuZV9jb250cm9sLA0KICB0dW5lR3JpZCA9IHR1bmVfZ3JpZDQsDQogIG1ldGhvZCA9ICJ4Z2JUcmVlIiwNCiAgdmVyYm9zZSA9IFRSVUUNCikNCg0KdHVuZXBsb3QoeGdiX3R1bmU0XzIwMjEpDQoNCnhnYl90dW5lNF8yMDIxJGJlc3RUdW5lDQoNCnZhckltcCh4Z2JfdHVuZTRfMjAyMSwgc2NhbGUgPSBGICApIA0KYGBgDQoNCiMjIyMgRmlmdGggVHVuaW5nOiBSZWR1Y2luZyB0aGUgTGVhcm5pbmcgUmF0ZSAgDQpOb3csIHdlIGhhdmUgdHVuZWQgdGhlIGh5cGVycGFyYW1ldGVycyBhbmQgY2FuIHN0YXJ0IHJlZHVjaW5nIHRoZSBsZWFybmluZyByYXRlIHRvIGdldCB0byB0aGUgZmluYWwgbW9kZWw6ICANCg0KYGBge3J9DQpzdGFydF90aW1lIDwtIFN5cy50aW1lKCkNCg0KdHVuZV9ncmlkNSA8LSBleHBhbmQuZ3JpZCgNCiAgbnJvdW5kcyA9IHNlcShmcm9tID0gMTAwLCB0byA9IDEwMDAwLCBieSA9IDc1KSwNCiAgIGV0YSA9IGMoMC4wMSwgMC4wMTUsIDAuMDI1LDAuMDM1LCAwLjA1LDAuNzUsIDAuMSksDQogIG1heF9kZXB0aCA9IHhnYl90dW5lMl8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCwNCiAgZ2FtbWEgPSB4Z2JfdHVuZTRfMjAyMSRiZXN0VHVuZSRnYW1tYSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHhnYl90dW5lM18yMDIxJGJlc3RUdW5lJGNvbHNhbXBsZV9ieXRyZWUsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSB4Z2JfdHVuZTJfMjAyMSRiZXN0VHVuZSRtaW5fY2hpbGRfd2VpZ2h0LA0KICBzdWJzYW1wbGUgPSB4Z2JfdHVuZTNfMjAyMSRiZXN0VHVuZSRzdWJzYW1wbGUNCikNCg0KDQoNCnhnYl90dW5lNV8yMDIxIDwtIGNhcmV0Ojp0cmFpbigNCiAgeCA9IGlucHV0X3gsDQogIHkgPSBpbnB1dF95LA0KICB0ckNvbnRyb2wgPSB0dW5lX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gdHVuZV9ncmlkNSwNCiAgbWV0aG9kID0gInhnYlRyZWUiLA0KICB2ZXJib3NlID0gVFJVRQ0KKQ0KDQojdHVuZXBsb3QoeGdiX3R1bmU1XzIwMjEpDQoNCmVuZF90aW1lIDwtIFN5cy50aW1lKCkNCg0KZW5kX3RpbWUgLSBzdGFydF90aW1lDQoNCnhnYl90dW5lNV8yMDIxJGJlc3RUdW5lDQoNCnZhckltcCh4Z2JfdHVuZTVfMjAyMSwgc2NhbGUgPSBGICApIA0KYGBgDQoNCg0KIyMjIyBGaXR0aW5nIEZpbmFsIE1vZGVsDQoNCmBgYHtyfQ0KDQooZmluYWxfZ3JpZF8yMDIxIDwtIGV4cGFuZC5ncmlkKA0KICBucm91bmRzID0geGdiX3R1bmU1XzIwMjEkYmVzdFR1bmUkbnJvdW5kcywNCiAgZXRhID0geGdiX3R1bmU1XzIwMjEkYmVzdFR1bmUkZXRhLA0KICBtYXhfZGVwdGggPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRtYXhfZGVwdGgsDQogIGdhbW1hID0geGdiX3R1bmU1XzIwMjEkYmVzdFR1bmUkZ2FtbWEsDQogIGNvbHNhbXBsZV9ieXRyZWUgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRjb2xzYW1wbGVfYnl0cmVlLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0geGdiX3R1bmU1XzIwMjEkYmVzdFR1bmUkbWluX2NoaWxkX3dlaWdodCwNCiAgc3Vic2FtcGxlID0geGdiX3R1bmU1XzIwMjEkYmVzdFR1bmUkc3Vic2FtcGxlDQopKQ0KDQooeGdiX21vZGVsXzIwMjEgPC0gY2FyZXQ6OnRyYWluKA0KICB4ID0gaW5wdXRfeCwNCiAgeSA9IGlucHV0X3ksDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gZmluYWxfZ3JpZF8yMDIxLA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopKQ0KDQp2YXJJbXAoeGdiX21vZGVsXzIwMjEsIHNjYWxlID0gRiAgKSANCg0KYGBgDQoNCiMjIyBNb2RlbCBQZXJmb3JtYW5jZSAgDQoNCg0KIyMjIyBDaGVja2luZyBNb2RlbCBvbiBUZXN0IFNwbGl0IERhdGEgIA0KDQpgYGB7cn0NCg0KDQp5X3ByZWRfdGVzdCA8LSBwcmVkaWN0KHhnYl9tb2RlbF8yMDIxLCBkYXRhLm1hdHJpeCh0ZV90cmVhdGVkXzIwMjEpKQ0KDQp0ZXN0X3N0YXRzPSBjYmluZCgodGVfdHJlYXRlZF8yMDIxJFdvcnRoKSx5X3ByZWRfdGVzdCkNCg0KdGVzdF9zdGF0c1IyID0gY29yKHRlc3Rfc3RhdHNbLDFdLHRlc3Rfc3RhdHNbLDJdKV4yDQoNCnByaW50KHRlc3Rfc3RhdHNSMikNCg0KDQp5X3ByZWRfdHJhaW4gPC0gcHJlZGljdCh4Z2JfbW9kZWxfMjAyMSwgZGF0YS5tYXRyaXgodHJfdHJlYXRlZF8yMDIxKSkNCg0KdHJhaW5fc3RhdHMgPSBjYmluZCgodHJfdHJlYXRlZF8yMDIxJFdvcnRoKSx5X3ByZWRfdHJhaW4pDQoNCnRyYWluX3N0YXRzUjIgPSBjb3IodHJhaW5fc3RhdHNbLDFdLHRyYWluX3N0YXRzWywyXSleMg0KDQpwcmludCh0cmFpbl9zdGF0c1IyKQ0KDQojdGVzdCBkYXRhc2V0DQp4IDwtIHNlbGVjdCh0ZV90cmVhdGVkXzIwMjEsIC1Xb3J0aCkNCnkgPC0gKHRlX3RyZWF0ZWRfMjAyMSRXb3J0aCkNCg0KKHhnYl9tb2RlbF9ybXNlIDwtIE1vZGVsTWV0cmljczo6cm1zZSh5LCBwcmVkaWN0KHhnYl9tb2RlbF8yMDIxLCBuZXdkYXRhID0geCkpKQ0KDQpob2xkb3V0X3ggPC0gc2VsZWN0KHRyX3RyZWF0ZWRfMjAyMSwgLVdvcnRoKQ0KaG9sZG91dF95IDwtIHRyX3RyZWF0ZWRfMjAyMSRXb3J0aA0KDQooeGdiX21vZGVsX3Jtc2UgPC0gTW9kZWxNZXRyaWNzOjpybXNlKGhvbGRvdXRfeSwgcHJlZGljdCh4Z2JfbW9kZWxfMjAyMSwgbmV3ZGF0YSA9IGhvbGRvdXRfeCkpKQ0KDQoNCmBgYA0KDQojIyMjIEdyYXBoaWNhbCBSZXByZXNlbnRhdGlvbiBvZiBNb2RlbCAgIA0KDQoNCmBgYHtyfQ0KDQpnZ3Bsb3QyOjpnZ3Bsb3QoKSArDQogIGFlcyh4ID0gdGVzdF9zdGF0c1ssMV0sIHkgPSB0ZXN0X3N0YXRzWywyXSkgKw0KICBnZW9tX2ppdHRlcigpICsNCiAgeGxhYigiUHJlZGljdGVkIFZhbHVlcyIpICsNCiAgeWxhYigiQWN0dWFsIFZhbHVlcyIpICsNCiAgZ2d0aXRsZSgiUmVzdWx0cyBvZiBQaXRjaGluZyBNb2RlbCBvbiBUZXN0IERhdGEiKSsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSxzaXplID0gMjIsY29sb3IgPSJzdGVlbCBibHVlIikpKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQ0KDQoNCg0KYGBgDQoNCg0KIyBDcmVhdGluZyAyMDIyIFByb2plY3Rpb25zIGZyb20gTW9kZWwgIHsudGFic2V0fSANCg0KDQojIyBSZS1maXQgbW9kZWwgZm9yIEltcG9ydGFudCBWYXJpYWJsZXMNCk5vdyB0aGF0IHdlIGhhdmUgYW4gYWNjZXB0YWJsZSBtb2RlbCwgd2UgY2FuIHVzZSBpdCB0byBjcmVhdGUgcHJvamVjdGlvbnMgZm9yIGhvdyB3ZWxsIHdlIHRoaW5rIHBsYXllcnMgc2hvdWxkIGRvIGluIDIwMjIgYmFzZWQgb24gdGhlaXIgaGl0dGluZyBzdGF0aXN0aWNzIGluIDIwMjEuIEZpcnN0IGxldCdzIHJlZHVjZQ0KDQoxLiBPbmx5IGtlZXAgdmFyaWFibGVzIHdpdGggaGlnaCBlbm91Z2ggaW1wb3J0YW5jZSBpbiBtb2RlbCAgDQoNCmBgYHtyfQ0KDQoNCnZpcCh4Z2JfbW9kZWxfMjAyMSwgbnVtX2ZlYXR1cmVzID0gMzApICAjIDEwIGlzIHRoZSBkZWZhdWx0LCAzMCBnaXZlcyBhIHZpc3VhbCBvbiB0aGUgdG9wIDMwIG1vc3QgaW1wb3J0YW50IGZlYXR1cmVzIG9mIHRoZSBtb2RlbA0KDQp1bnNjYWxldmkgPSB2aSh4Z2JfbW9kZWxfMjAyMSwgbWV0aG9kPSJtb2RlbCIpICNzaG93cyB0aGUgbnVtYmVycyBiZWhpbmQgdGhlIHBsb3QNCg0KdW5zY2FsZXZpJEltcG9ydGFuY2VfcGVyYyA9IHdpdGgodW5zY2FsZXZpLEltcG9ydGFuY2Uvc3VtKEltcG9ydGFuY2UpKSAjYWRkcyBwZXJjZW50YWdlcyANCg0KdW5zY2FsZXZpICMgaW1wb3J0YW5jZSBieSB2YXJpYWJsZXMNCg0KdmFyaWFibGVzX3RvX2tlZXBfMjAyMSA9IHN1YnNldCh1bnNjYWxldmksIEltcG9ydGFuY2VfcGVyYyA+IDAuMDAxMCkgJT4lIHNlbGVjdChWYXJpYWJsZSkgI0tlZXAgVmFyaWFibGVzIHRoYXQgZXhwbGFpbiBhdCBsZWFzdCBhIHNtYWxsIGFtb3VudCBbMC4xJV0gb2YgdGhlIG1vZGVsLiBUaGlzIGlzIGEgbG93IHRocmVzaG9sZCBmb3IgaW5jbHVzaW9uICxidXQgeW91IGNhbiBhZGp1c3QgdGhpcw0KDQp2YXJpYWJsZXNfdG9fa2VlcF8yMDIxYiA9IHQodmFyaWFibGVzX3RvX2tlZXBfMjAyMSkNCg0KdmFyaWFibGVzX3RvX2tlZXBfMjAyMiA9IGNvbG5hbWVzKHJvd190b19uYW1lcyh2YXJpYWJsZXNfdG9fa2VlcF8yMDIxYixyb3dfbnVtYmVyID0gMSkpDQoNCnRyX3RyZWF0ZWRfMjAyMiA9IHRyX3RyZWF0ZWRfMjAyMSAlPiUgIHNlbGVjdChXb3J0aCxvbmVfb2YodmFyaWFibGVzX3RvX2tlZXBfMjAyMiksc3RhcnRzX3dpdGgoIlRlYW1fbGV2X3hfIikpICNrZWVwIG1vZGVsZWQgaW1wb3J0YW50IHZhcmlhYmxlcyBhbG9uZyB3aXRoIHRlYW0gaW5kaWNhdG9yIHZhcmlhYmxlcw0KDQp0ZV90cmVhdGVkXzIwMjIgPSB0ZV90cmVhdGVkXzIwMjEgJT4lICBzZWxlY3QoV29ydGgsb25lX29mKHZhcmlhYmxlc190b19rZWVwXzIwMjIpLHN0YXJ0c193aXRoKCJUZWFtX2xldl94XyIpKQ0KDQppbnB1dF94XzIwMjIgPSBhcy5tYXRyaXgoc2VsZWN0KHRyX3RyZWF0ZWRfMjAyMiwgLVdvcnRoKSkNCg0KaW5wdXRfeV8yMDIyID0gdHJfdHJlYXRlZF8yMDIyJFdvcnRoDQoNCg0KDQpgYGANCg0KMi4gUmUtZml0IG1vZGVsIHdpdGggcmVkdWNlZCB2YXJpYWJsZSBzY29wZSAgDQoNCmBgYHtyfQ0KDQoNCihmaW5hbF9ncmlkXzIwMjEgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRucm91bmRzLA0KICBldGEgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRldGEsDQogIG1heF9kZXB0aCA9IHhnYl90dW5lNV8yMDIxJGJlc3RUdW5lJG1heF9kZXB0aCwNCiAgZ2FtbWEgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRnYW1tYSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHhnYl90dW5lNV8yMDIxJGJlc3RUdW5lJGNvbHNhbXBsZV9ieXRyZWUsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRtaW5fY2hpbGRfd2VpZ2h0LA0KICBzdWJzYW1wbGUgPSB4Z2JfdHVuZTVfMjAyMSRiZXN0VHVuZSRzdWJzYW1wbGUNCikpDQoNCih4Z2JfbW9kZWxfMjAyMiA8LSBjYXJldDo6dHJhaW4oDQogIHggPSBpbnB1dF94XzIwMjIsDQogIHkgPSBpbnB1dF95XzIwMjIsDQogIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogIHR1bmVHcmlkID0gZmluYWxfZ3JpZF8yMDIxLA0KICBtZXRob2QgPSAieGdiVHJlZSIsDQogIHZlcmJvc2UgPSBUUlVFDQopKQ0KDQoNCnZpcCh4Z2JfbW9kZWxfMjAyMiwgbnVtX2ZlYXR1cmVzID0gMzApDQoNCnVuc2NhbGV2aTI0ID0gdmkoeGdiX21vZGVsXzIwMjIsIG1ldGhvZD0ibW9kZWwiKQ0KDQp1bnNjYWxldmkyNCRJbXBvcnRhbmNlX3BlcmMgPSB3aXRoKHVuc2NhbGV2aTI0LEltcG9ydGFuY2Uvc3VtKEltcG9ydGFuY2UpKSANCg0KdW5zY2FsZXZpMjQNCg0KDQojRm9yIGFueXRoaW5nIGFib3ZlIGJyZWFraW5nX0lQIHdlIG5lZWQgdG8gY3JlYXRlIHByb2plY3Rpb24gdGFibGUgYnkgYWdlIG9yIGFnZSBidWNrZXQNCg0KI3dyaXRlX2Nzdih1bnNjYWxldmkyNCwidW5zY2FsZXZpMjQuY3N2IikNCg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyAyMDIyIFByb2plY3Rpb25zIEZ1bGwgIA0KDQpGaXJzdCBsZXQncyBwcmVwYXJlIGEgZmlsZSBmb3IgcHJlZGljdGluZyBiYXNlZCBvbiBvdXIgbW9kZWwgb2JqZWN0DQoNCmBgYHtyfQ0KDQoNCnZhcmlhYmxlc2xhZzV4Yj0gcm93X3RvX25hbWVzKGFzLmRhdGEuZnJhbWUodCh2YXJpYWJsZXNfdG9fa2VlcF8yMDIyKSkscm93X251bWJlciA9IDEpICAlPiUgc2VsZWN0IChzdGFydHNfd2l0aCgibGFnIikpDQoNCnZhcmlhYmxlc19ub2xhZzV4YiA9IChvd21yOjpyZW1vdmVfcHJlZml4KHZhcmlhYmxlc2xhZzV4YiwibGFnIiAsIHNlcCA9ICJfIikpDQoNCkRhdGFfUHJlZGljdF8yMDIyYTV4YiA9IHRvdGFsX3RyZWF0ZWRfMjAyMV9oaXR0aW5nICU+JSBzZWxlY3QgKG9uZV9vZihjb2xuYW1lcyh2YXJpYWJsZXNfbm9sYWc1eGIpKSxTZWFzb24scGxheWVyaWQpDQoNCmNvbG5hbWVzKERhdGFfUHJlZGljdF8yMDIyYTV4YikgPC0gcGFzdGUwKCJsYWdfIiwgY29sbmFtZXMoRGF0YV9QcmVkaWN0XzIwMjJhNXhiKSkNCg0KRGF0YV9QcmVkaWN0XzIwMjJiNXhiID0gdG90YWxfdHJlYXRlZF8yMDIxX2hpdHRpbmcgJT4lIHNlbGVjdCAob25lX29mKGNvbG5hbWVzKHZhcmlhYmxlc19ub2xhZzV4YikpKQ0KY29sbmFtZXMoRGF0YV9QcmVkaWN0XzIwMjJiNXhiKSA9IGNvbG5hbWVzKHZhcmlhYmxlc2xhZzV4YikNCg0KdmFyaWFibGVzX3RvX2tlZXBfMjAyMl9ub2xhZzV4YiA9IHRvdGFsX3RyZWF0ZWRfMjAyMV9oaXR0aW5nICU+JSBzZWxlY3Qob25lX29mKHZhcmlhYmxlc190b19rZWVwXzIwMjIpLFNlYXNvbixwbGF5ZXJpZCxzdGFydHNfd2l0aCgiVGVhbV9sZXZfeF8iKSklPiUgc2VsZWN0KC1vbmVfb2YoY29sbmFtZXMoRGF0YV9QcmVkaWN0XzIwMjJiNXhiKSkpDQoNCg0KRGF0YV9wcmVkaWN0XzIwMjI1eGIgPSBzcWxkZigNCiAgIg0KICBzZWxlY3QgYS4qLGIuKiBmcm9tDQogIERhdGFfUHJlZGljdF8yMDIyYTV4YiBhLA0KICB2YXJpYWJsZXNfdG9fa2VlcF8yMDIyX25vbGFnNXhiIGINCiAgb24gYi5wbGF5ZXJpZCA9IGEubGFnX3BsYXllcmlkDQogIGFuZCBiLlNlYXNvbiA9IGEubGFnX1NlYXNvbg0KICAiDQopICU+JSBzZWxlY3QoLWxhZ19wbGF5ZXJpZCxsYWdfU2Vhc29uKSAlPiUNCiAgZmlsdGVyKFNlYXNvbiA9PSAyMDIxKSAlPiUgDQogIHNlbGVjdChvbmVfb2YodmFyaWFibGVzX3RvX2tlZXBfMjAyMiksc3RhcnRzX3dpdGgoIlRlYW1fbGV2X3hfIikpDQoNCg0KDQpgYGANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIENyZWF0ZSBQcmVkaWN0aW9ucyBmb3IgTW9kZWwNCg0KIyMjIFJ1biBQcm9qZWN0aW9ucyBvbiBQbGF5ZXJzIHdobyBQbGF5ZWQgaW4gMjAyMQ0KDQpUaGlzIGlzIHRoZSByYXcgcHJlZGljdGlvbiBzY29yZSBwZXIgSVAgZm9yIGVhY2ggcGl0Y2hlcg0KDQpgYGB7cn0NCg0KaGl0dGluZ19wcmVkaWN0aW9uczV4YiA9IGFzLmRhdGEuZnJhbWUocHJlZGljdCh4Z2JfbW9kZWxfMjAyMixEYXRhX3ByZWRpY3RfMjAyMjV4YikpDQoNCm5hbWVzKGhpdHRpbmdfcHJlZGljdGlvbnM1eGIpID0gYygiUHJlZGljdF9TY29yZSIpDQoNCkRhdGFfcHJlZGljdF8yMDIyX3dfaGl0dGluZ19QcmVkaWN0aW9uczV4YiA9IGNiaW5kKERhdGFfcHJlZGljdF8yMDIyNXhiLGhpdHRpbmdfcHJlZGljdGlvbnM1eGIpICU+JSBzZWxlY3QocGxheWVyaWQsUHJlZGljdF9TY29yZSkNCg0KaGVhZChEYXRhX3ByZWRpY3RfMjAyMl93X2hpdHRpbmdfUHJlZGljdGlvbnM1eGIpDQoNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KDQpgYGB7cn0NCg0KTGF0ZXN0XzIwMjJfaGl0dGluZ2RhdGFfRlAgPSByZWFkX2NzdigiRmFuR3JhcGhfRmFudGFzeV9CYXNlYmFsbF9IaXR0aW5nLmNzdiIpDQoNCkxhdGVzdF8yMDIyX2hpdHRpbmdkYXRhX0ZQDQoNCg0KYGBgDQoNCg0KDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpgYGB7ciwgd2FybmluZyA9IEZhbHNlfQ0KDQoNCmhpdHRpbmdfRGF0YV9Ob25BZGpfUHJvamVjdGlvbnM1eGIgPSBzcWxkZigNCiAgIg0KICBzZWxlY3QgYS4qLGIuUHJlZGljdF9TY29yZQ0KICBmcm9tIExhdGVzdF8yMDIyX2hpdHRpbmdkYXRhX0ZQIGEgDQogIGxlZnQgam9pbiANCiAgRGF0YV9wcmVkaWN0XzIwMjJfd19oaXR0aW5nX1ByZWRpY3Rpb25zNXhiIGINCiAgb24gYS5wbGF5ZXJpZCA9IGIucGxheWVyaWQNCiAgIg0KKSAlPiUgZmlsdGVyKEFEUDwzNzAgfCBpcy5uYShQcmVkaWN0X1Njb3JlKT09RikNCg0KDQpoaXR0aW5nX0RhdGFfQWRqX1Byb2plY3Rpb25zNXhiID0NCmhpdHRpbmdfRGF0YV9Ob25BZGpfUHJvamVjdGlvbnM1eGIgJT4lIA0KICBtdXRhdGUoDQogICAgQXZnX1BBID0gMzAwLA0KICAgIEFkalByZWRpY3RfU2NvcmVfcmF3ID0gaWZlbHNlKGlzLm5hKFByZWRpY3RfU2NvcmUpLE5BLFByZWRpY3RfU2NvcmUqKFBBL0F2Z19QQSkpLA0KICAgIG1heF9wcmVkc2NvcmU9IG1heChBZGpQcmVkaWN0X1Njb3JlX3JhdyxuYS5ybSA9IFQpLA0KICAgIEFkalByZWRpY3RfU2NvcmUgPSBpZmVsc2UgKGlzLm5hKEFkalByZWRpY3RfU2NvcmVfcmF3KSxOQSxBZGpQcmVkaWN0X1Njb3JlX3JhdyAqMTAwL21heF9wcmVkc2NvcmUpLA0KICAgIFdBUl9yYW5rID0gb3JkZXIob3JkZXIocmFuayhXQVIsdGllcy5tZXRob2QgPSAnYXZlcmFnZScpLGRlY3JlYXNpbmcgPSBUUlVFKSksDQogICAgQWRqUHJlZGljdF9TY29yZV9SYW5rID0gb3JkZXIob3JkZXIocmFuayhBZGpQcmVkaWN0X1Njb3JlLHRpZXMubWV0aG9kID0gJ2F2ZXJhZ2UnKSxkZWNyZWFzaW5nID0gVFJVRSkpLXN1bShpcy5uYShBZGpQcmVkaWN0X1Njb3JlKSkNCiAgKSAlPiUgc2VsZWN0IChOYW1lLEFEUCxXQVIsIFdBUl9yYW5rLEFkalByZWRpY3RfU2NvcmUgLEFkalByZWRpY3RfU2NvcmVfUmFuaykNCg0KDQogIA0KDQpnZ3Bsb3QyOjpxcGxvdChoaXR0aW5nX0RhdGFfQWRqX1Byb2plY3Rpb25zNXhiJEFkalByZWRpY3RfU2NvcmUsIG1haW49IlByZWRpY3Rpb25zIikgKyBnZW9tX2hpc3RvZ3JhbShjb2xvdXI9ImJsYWNrIiwgZmlsbD0iZ3JleSIpICsgdGhlbWVfYncoKQ0KDQoNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyAyMDIyIFByb2plY3Rpb25zIEZ1bGwNCg0KIyMgVGFibGUgb2YgaGl0dGluZyBQcm9qZWN0aW9ucyAoUGxheWVycyB3aG8gRGlkbid0IFBsYXkgaW4gMjAyMSAtIFJlY2lldmUgYW4gTkEpDQoNCkFkalByZWRpY3RfU2NvcmUgYXJlIG5vcm1hbGl6ZWQgdG8gMTAwDQoNCmBgYHtyfQ0KDQp0YWJsZWV4cG9ydCA9DQpoaXR0aW5nX0RhdGFfQWRqX1Byb2plY3Rpb25zNXhiICU+JQ0KICBhcnJhbmdlIChBRFAsV0FSKSAlPiUgDQogIGtibCgpICU+JSANCiBrYWJsZV9tYXRlcmlhbChjKCJzdHJpcGVkIiwgImhvdmVyIiwiY29uZGVuc2VkIiwicmVzcG9uc2l2ZSIpLGZ1bGxfd2lkdGggPSBGLGZpeGVkX3RoZWFkID0gVCkNCg0Kc2F2ZV9rYWJsZSh0YWJsZWV4cG9ydCxmaWxlID0gImhpdHRpbmc1eDUuaHRtbCIpDQoNCiN0YWJsZWV4cG9ydA0KDQoNCg0KYGBgDQoNClRoaXMgaXMgYSBiZXR0ZXIgZm9ybWF0dGVkIFRhYmxlDQoNCmBgYHtyICwgd2FybmluZz1GQUxTRX0NCg0KDQpmdF9kdCA8LSBoaXR0aW5nX0RhdGFfQWRqX1Byb2plY3Rpb25zNXhiWzE6bnJvdyhoaXR0aW5nX0RhdGFfQWRqX1Byb2plY3Rpb25zNXhiKSwgMTpuY29sKGhpdHRpbmdfRGF0YV9BZGpfUHJvamVjdGlvbnM1eGIpXQ0KDQpmdF9kdCRBRFAgPC0gY29sb3JfdGlsZSgid2hpdGUiLCAicmVkIikoZnRfZHQkQURQKQ0KDQpmdF9kdCRXQVIgPC0gY29sb3JfYmFyKCJsaWdodGJsdWUiKShmdF9kdCRXQVIpDQoNCmZ0X2R0JFByZWRpY3RfU2NvcmUgPC0gY2VsbF9zcGVjKHJvdW5kKGZ0X2R0JEFkalByZWRpY3RfU2NvcmUsMSksIGFuZ2xlID0gKDE6NSkqMCwgDQogICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gImdyZWVuIiwgY29sb3IgPSAid2hpdGUiLCBhbGlnbiA9ICJjZW50ZXIiKQ0KDQpmdF9kdCRXQVJfUmFuayA8LSBjb2xvcl90aWxlKCJncmVlbiIsIm9yYW5nZSIpKGZ0X2R0JFdBUl9yYW5rKQ0KDQpmdF9kdCRQcmVkaWN0X1JhbmsgPC0gY29sb3JfdGlsZSgiZ3JlZW4iLCJvcmFuZ2UiKShmdF9kdCRBZGpQcmVkaWN0X1Njb3JlX1JhbmspDQoNCmZ0X2R0IDwtIGZ0X2R0W2MoIk5hbWUiLCAiQURQIiwgIldBUiIsICJQcmVkaWN0X1Njb3JlIiwiV0FSX1JhbmsiLCJQcmVkaWN0X1JhbmsiKV0gJT4lIGFycmFuZ2UgKGRlc2MoQURQKSkNCg0KDQp0YWJsZV9leHBvcnQgPSANCmtibChmdF9kdCwgZXNjYXBlID0gRikgJT4lIA0KIGthYmxlX21hdGVyaWFsKGMoInN0cmlwZWQiLCAiaG92ZXIiLCJjb25kZW5zZWQiLCJyZXNwb25zaXZlIiksZnVsbF93aWR0aCA9IEYsZml4ZWRfdGhlYWQgPSBUKSAlPiUgICBjb2x1bW5fc3BlYyg2LCB3aWR0aCA9ICIzY20iKSAlPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIiwgIlNjb3JlcyIgPSAzLCAiUmFua3MiID0gMikpDQoNCnNhdmVfa2FibGUodGFibGVfZXhwb3J0LGZpbGUgPSAiaGl0dGluZzV4NV91cGRhdGVkLmh0bWwiKQ0KICANCnRhYmxlX2V4cG9ydCAgDQoNCg0KDQoNCg0KYGBgDQoNCg0KDQoNCg0KDQoNCjwvaHRtbD4NCg==